Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5157732
Add custom titlebar with custom window controls
bennyscripts Jan 20, 2026
dfe219c
Move console into home page and remove discord and ghost details cards
bennyscripts Jan 20, 2026
1f2c55f
Make dark colours darker and remove dedicated quit button in the sidebar
bennyscripts Jan 20, 2026
c4a44f4
Fix padding issues in scripts page
bennyscripts Jan 20, 2026
589f231
Disable maximise button
bennyscripts Jan 20, 2026
aa2b9ca
Add resizing - VERY BUGGY
bennyscripts Jan 20, 2026
cd4da36
Change ttkbootstrap version to 1.14.2
bennyscripts Jan 20, 2026
ef4a39e
Fix transparency on Windows
Jan 20, 2026
52bdf37
Fix padding, fonts and added icon to titlebar for windows
Jan 20, 2026
491765d
Fix transparency on Linux
bennyscripts Jan 20, 2026
3351c4f
Fixed visual bugs when resizing, resize grips are now made once and o…
bennyscripts Jan 20, 2026
f26879c
Hide window until fully ready
bennyscripts Jan 20, 2026
1e919cc
Add rounded corners
bennyscripts Jan 20, 2026
dd047ed
Fix problems when minimising window
bennyscripts Jan 20, 2026
af2b0b9
Change header font to match title in home page
bennyscripts Jan 20, 2026
807306e
Add global Style enum for globally shared colours that are not part o…
bennyscripts Jan 20, 2026
bc9ca05
Fix minimising window on Windows
bennyscripts Jan 20, 2026
83d97c0
Change font sizes
bennyscripts Jan 20, 2026
3edf99d
Add dedicated close function and make mac os close button withdraw to…
bennyscripts Jan 20, 2026
4254f50
Custom dropdown menu widget
bennyscripts Jan 21, 2026
a31f7b9
Add resize grips to tools pages
bennyscripts Jan 21, 2026
e69bba9
Disable create theme button when name entry is empty
bennyscripts Jan 21, 2026
cf2dcc3
Add switch for toggling RPC and session spoofing
bennyscripts Jan 21, 2026
77aff60
Add state to rounded button
bennyscripts Jan 21, 2026
ba4bd52
Completely redesigned settings page
bennyscripts Jan 21, 2026
019dd3e
Change button radius and colour
bennyscripts Jan 21, 2026
41d27c3
Add right-chevron-tiny
bennyscripts Jan 21, 2026
cb7469d
Change size of inactive window controls on mac os
bennyscripts Jan 21, 2026
906c1e8
Add lift to rounded frame
bennyscripts Jan 21, 2026
e1ae991
Add version to Mac OS Info.plist
bennyscripts Jan 21, 2026
1db6f74
Add page title to scripts page
bennyscripts Jan 22, 2026
ee201ff
Move scripts page to bottom of sidebar
bennyscripts Jan 22, 2026
d88e732
Redesign tools to be a grid
bennyscripts Jan 22, 2026
9c6cdb2
Remove .lower from embeds
bennyscripts Jan 22, 2026
263320b
Add Key to SerpAPI label
bennyscripts Jan 22, 2026
85a31b2
Add load_image_from_url method
bennyscripts Jan 22, 2026
8568608
Add rich presence preview to gui
bennyscripts Jan 22, 2026
2f6d8d9
Add try except around bind all
bennyscripts Feb 15, 2026
67d33a6
Add caching for avatar images
bennyscripts Feb 15, 2026
d614590
Add caching for images and colors from url
bennyscripts Feb 15, 2026
803c0d0
Pre load images required for RPC page for snappier performance
bennyscripts Feb 15, 2026
6bd292b
Remove message style in save theme
bennyscripts Feb 15, 2026
c0e4b6a
Add transparancy and gif support to image embeds
bennyscripts Feb 15, 2026
9059b8a
Add center square crop for thumbnails
bennyscripts Feb 15, 2026
9535e62
Remove gif debug msg
bennyscripts Feb 15, 2026
5640e76
Adjust colour pallette to have a blue hue
bennyscripts Feb 15, 2026
f1011b2
Rename spy.pet to surveillance
bennyscripts Feb 15, 2026
6246b36
Remove custom titlebar on windows and linux
bennyscripts Feb 15, 2026
243019c
Add clean and noconfirm args to pyinstaller
Feb 15, 2026
cb4e3a5
Remove transparency on windows and linux
Feb 15, 2026
9dae486
Change default window size to be bigger on Windows
Feb 15, 2026
ebf3a0f
Change padding on sidebar on Windows
Feb 15, 2026
5c7639d
Use original layout without border if on Windows
Feb 15, 2026
d2efcb4
Adjust placement of user wrapper and message stats on Windows
Feb 15, 2026
f9f68c1
Make sidebar icons bigger to better fit the font size on Windows
Feb 15, 2026
1a71be9
Adjust font and image sizes to be cohesive
Feb 15, 2026
7485402
Change secondary, entry bg and tool hover colour
bennyscripts Feb 15, 2026
147711a
Make tools page not scrollable, not enough tools to warrent it
bennyscripts Feb 15, 2026
05b2335
Change avatar size to better fit the new username size
bennyscripts Feb 15, 2026
a742146
Change pagination titles for tools pages
bennyscripts Feb 15, 2026
4421d89
New sniper card design
bennyscripts Feb 15, 2026
4159962
Change tools card colours
bennyscripts Feb 15, 2026
dda8eb0
Add details back to home page
bennyscripts Feb 15, 2026
83cd380
V4.1.0 IS READY
bennyscripts Feb 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ async def on_ready(self):

await self._setup_scripts()
await self.controller.setup_webhooks()
self.controller.spypet.set_bot(self)
self.controller.surveillance.set_bot(self)

except Exception as e:
console.print_error(str(e))
Expand Down
275 changes: 141 additions & 134 deletions bot/commands/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,157 +345,164 @@ async def commandhistory(self, ctx):
description=description
), delete_after=cfg.get("message_settings")["auto_delete_delay"])

@commands.command(name="spypet", description="Get a list of every message a member has sent in mutual servers.", usage="[member]")
async def spypet(self, ctx, member_id: int):
mutual_guilds = [guild for guild in self.bot.guilds if guild.get_member(member_id)]
data = {}
tasks = []
sem = asyncio.Semaphore(10)
stop_event = asyncio.Event()
last_saved_count = 0

def _count_messages():
return sum(len(channels) for guilds in data.values() for channels in guilds.values())

def _save_data():
nonlocal last_saved_count
current_count = _count_messages()
if current_count == last_saved_count:
return
last_saved_count = current_count
@commands.command(name="surveillance", description="Get a list of every message a member has sent in mutual servers.", usage="[member]")
async def surveillance(self, ctx, member_id: int = None):
await cmdhelper.send_message(ctx, {
"title": "Surveillance",
"description": "This is a GUI only feature, please open the GUI to use it.",
"colour": "#ff0000"
})
return

# mutual_guilds = [guild for guild in self.bot.guilds if guild.get_member(member_id)]
# data = {}
# tasks = []
# sem = asyncio.Semaphore(10)
# stop_event = asyncio.Event()
# last_saved_count = 0

# def _count_messages():
# return sum(len(channels) for guilds in data.values() for channels in guilds.values())

# def _save_data():
# nonlocal last_saved_count
# current_count = _count_messages()
# if current_count == last_saved_count:
# return
# last_saved_count = current_count

with open(files.get_application_support() + "/data/spypet.json", "w") as f:
json.dump(data, f, indent=4)
console.print_info(f"Auto-saved {current_count} messages.")

async def _autosave(interval=5):
while not stop_event.is_set():
await asyncio.sleep(interval)
_save_data()
# with open(files.get_application_support() + "/data/surveillance.json", "w") as f:
# json.dump(data, f, indent=4)
# console.print_info(f"Auto-saved {current_count} messages.")

# async def _autosave(interval=5):
# while not stop_event.is_set():
# await asyncio.sleep(interval)
# _save_data()

console.print_info("Auto-saving stopped. Spypet complete!")
# console.print_info("Auto-saving stopped. Surveillance complete!")

def _add_message(guild, channel, message):
if guild.name not in data: data[guild.name] = {}
if channel.name not in data[guild.name]: data[guild.name][channel.name] = []
# def _add_message(guild, channel, message):
# if guild.name not in data: data[guild.name] = {}
# if channel.name not in data[guild.name]: data[guild.name][channel.name] = []

data[guild.name][channel.name].append(message)
# _save_data()
# data[guild.name][channel.name].append(message)
# # _save_data()

def _get_permissions(channel):
member = channel.guild.get_member(member_id)
member_role = member.top_role
bot_role = channel.guild.me.top_role
# def _get_permissions(channel):
# member = channel.guild.get_member(member_id)
# member_role = member.top_role
# bot_role = channel.guild.me.top_role

return (channel.permissions_for(member).read_messages and
channel.permissions_for(channel.guild.me).read_messages or
channel.overwrites_for(member_role).read_messages and
channel.overwrites_for(bot_role).read_messages)
# return (channel.permissions_for(member).read_messages and
# channel.permissions_for(channel.guild.me).read_messages or
# channel.overwrites_for(member_role).read_messages and
# channel.overwrites_for(bot_role).read_messages)


async def _fetch_context_channel(channel):
if channel.id == ctx.channel.id:
return ctx.channel
try:
latest_msg = [msg async for msg in channel.history(limit=1)][0]
context = await self.bot.get_context(latest_msg)
# async def _fetch_context_channel(channel):
# if channel.id == ctx.channel.id:
# return ctx.channel
# try:
# latest_msg = [msg async for msg in channel.history(limit=1)][0]
# context = await self.bot.get_context(latest_msg)

console.success(f"Got context for {channel.guild.name} - {channel.name}")
# console.success(f"Got context for {channel.guild.name} - {channel.name}")

return context.channel
except Exception as e:
if "429" in str(e):
console.error(f"Rate limited while fetching context for {channel.guild.name} - {channel.name}")
await asyncio.sleep(5)
return await _fetch_context_channel(channel)
# return context.channel
# except Exception as e:
# if "429" in str(e):
# console.error(f"Rate limited while fetching context for {channel.guild.name} - {channel.name}")
# await asyncio.sleep(5)
# return await _fetch_context_channel(channel)

return None

async def _get_messages(channel, delay=0.25, oldest_first=False):
async with sem:
try:
await asyncio.sleep(delay)
console.print_info(f"Finding messages in {channel.guild.name} - {channel.name}")

channel = await _fetch_context_channel(channel) or channel
guild = channel.guild
messages = []

try:
async for msg in channel.history(limit=None, oldest_first=oldest_first):
print(channel.name, msg.author.id, msg.content)
if msg.author.id == member_id:
if len(msg.attachments) > 0:
attachments = "\n".join([f"Attachment: {attachment.url}" for attachment in msg.attachments])
msg_string = f"[{msg.created_at.strftime('%Y-%m-%d %H:%M:%S')}] {msg.content}\n{attachments}"
else:
msg_string = f"[{msg.created_at.strftime('%Y-%m-%d %H:%M:%S')}] {msg.content}"
messages.append(msg_string)
_add_message(guild, channel, msg_string)
console.print_info(f"Found message in {channel.guild.name} - {channel.name}")
# return None

# async def _get_messages(channel, delay=0.25, oldest_first=False):
# async with sem:
# try:
# await asyncio.sleep(delay)
# console.print_info(f"Finding messages in {channel.guild.name} - {channel.name}")

# channel = await _fetch_context_channel(channel) or channel
# guild = channel.guild
# messages = []

# try:
# async for msg in channel.history(limit=None, oldest_first=oldest_first):
# print(channel.name, msg.author.id, msg.content)
# if msg.author.id == member_id:
# if len(msg.attachments) > 0:
# attachments = "\n".join([f"Attachment: {attachment.url}" for attachment in msg.attachments])
# msg_string = f"[{msg.created_at.strftime('%Y-%m-%d %H:%M:%S')}] {msg.content}\n{attachments}"
# else:
# msg_string = f"[{msg.created_at.strftime('%Y-%m-%d %H:%M:%S')}] {msg.content}"
# messages.append(msg_string)
# _add_message(guild, channel, msg_string)
# console.print_info(f"Found message in {channel.guild.name} - {channel.name}")

if len(messages) > 0:
console.print_success(f"Found messages in {channel.guild.name} - {channel.name}")
else:
console.print_error(f"Found no messages in {channel.guild.name} - {channel.name}")
except Exception as e:
if "429" in str(e).lower():
console.print_error("Rate limited! Waiting for 5 seconds...")
await asyncio.sleep(5)
return await _get_messages(channel, delay)
else:
console.print_error(f"Error in {channel.guild.name} - {channel.name}: {e}")
return

_save_data()
except asyncio.CancelledError:
console.print_warning("Process was cancelled! Saving progress...")
stop_event.set()
_save_data()
raise
except Exception as e:
console.print_error(f"Error in {channel.guild.name} - {channel.name}: {e}")
finally:
_save_data()

async def _attempt_scrape(guild, delay):
tasks = []
console.info(f"Attempting to scrape {guild.name} - {guild.id}")
# if len(messages) > 0:
# console.print_success(f"Found messages in {channel.guild.name} - {channel.name}")
# else:
# console.print_error(f"Found no messages in {channel.guild.name} - {channel.name}")
# except Exception as e:
# if "429" in str(e).lower():
# console.print_error("Rate limited! Waiting for 5 seconds...")
# await asyncio.sleep(5)
# return await _get_messages(channel, delay)
# else:
# console.print_error(f"Error in {channel.guild.name} - {channel.name}: {e}")
# return

# _save_data()
# except asyncio.CancelledError:
# console.print_warning("Process was cancelled! Saving progress...")
# stop_event.set()
# _save_data()
# raise
# except Exception as e:
# console.print_error(f"Error in {channel.guild.name} - {channel.name}: {e}")
# finally:
# _save_data()

# async def _attempt_scrape(guild, delay):
# tasks = []
# console.info(f"Attempting to scrape {guild.name} - {guild.id}")

for channel in guild.channels:
if isinstance(channel, discord.TextChannel) and _get_permissions(channel):
tasks.append(_get_messages(channel, delay, oldest_first=True))
delay += 2
# for channel in guild.channels:
# if isinstance(channel, discord.TextChannel) and _get_permissions(channel):
# tasks.append(_get_messages(channel, delay, oldest_first=True))
# delay += 2

if len(tasks) == 0:
console.print_error(f"No valid channels in {guild.name} - {guild.id}")
return
# if len(tasks) == 0:
# console.print_error(f"No valid channels in {guild.name} - {guild.id}")
# return

try:
await asyncio.gather(*tasks)
except asyncio.CancelledError:
console.print_warning("Process was cancelled! Saving progress...")
stop_event.set()
_save_data()
raise

delay = 1
autosave_task = asyncio.create_task(_autosave(5))
# try:
# await asyncio.gather(*tasks)
# except asyncio.CancelledError:
# console.print_warning("Process was cancelled! Saving progress...")
# stop_event.set()
# _save_data()
# raise

# delay = 1
# autosave_task = asyncio.create_task(_autosave(5))

for guild in mutual_guilds:
tasks.append(_attempt_scrape(guild, delay))
delay += 1.5

await asyncio.gather(*tasks)
stop_event.set()
await autosave_task
_save_data()
# for guild in mutual_guilds:
# tasks.append(_attempt_scrape(guild, delay))
# delay += 1.5

# await asyncio.gather(*tasks)
# stop_event.set()
# await autosave_task
# _save_data()

console.print_success("Spypet complete! Data saved to data/spypet.json.")
console.print_info(f"Total messages: {_count_messages()}")
console.print_info(f"Total guilds: {len(data)}")
console.print_info(f"Total channels: {sum(len(channels) for channels in data.values())}")
await ctx.send(file=discord.File(files.get_application_support() + "/data/spypet.json"), delete_after=self.cfg.get("message_settings")["auto_delete_delay"])
# console.print_success("Spypet complete! Data saved to data/surveillance.json.")
# console.print_info(f"Total messages: {_count_messages()}")
# console.print_info(f"Total guilds: {len(data)}")
# console.print_info(f"Total channels: {sum(len(channels) for channels in data.values())}")
# await ctx.send(file=discord.File(files.get_application_support() + "/data/surveillance.json"), delete_after=self.cfg.get("message_settings")["auto_delete_delay"])

@commands.command(name="latency", description="Check the bot's latency", usage="")
async def latency(self, ctx):
Expand Down
Loading