Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ async def on_message(self, message):
async def on_ready(self):
try:
self.start_time = time.time()

is_secondary = getattr(self.controller, '_is_secondary', False)

if is_secondary:
self.controller.bot_running = True
await self.load_cogs()
return

self.cfg.add_token(self.cfg.get("token"), self.user.name, self.user.id)
await self.load_cogs()

Expand All @@ -168,7 +176,6 @@ async def on_ready(self):

if self.session_spoofing:
console.success(f"Spoofing session as {self.session_spoofing_device}")
# console.print_warning("Your account is at higher risk of termination by using session spoofer.")

await self._setup_scripts()
await self.controller.setup_webhooks()
Expand All @@ -177,6 +184,9 @@ async def on_ready(self):
except Exception as e:
console.print_error(str(e))

if is_secondary:
return

cfg_rpc = self.cfg.get_rich_presence()
if cfg_rpc.enabled:
external_assets = {
Expand Down
84 changes: 61 additions & 23 deletions gui/components/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from utils.console import get_formatted_time

class Console:
MAX_LOG_ENTRIES = 500

def __init__(self, root, bot_controller):
self.root = root
self.bot = bot_controller
Expand All @@ -18,64 +20,98 @@ def __init__(self, root, bot_controller):
self.non_darwin_font_size = 12

def update(self):
if self.textarea is None:
return
try:
self.textarea.delete("1.0", "end")

for time, prefix, text in self.console:
visible = self.console[-self.MAX_LOG_ENTRIES:] if len(self.console) > self.MAX_LOG_ENTRIES else self.console

for time, prefix, text in visible:
if prefix.lower() == "sniper":
content = text
type = content.get("type")
type_name = content.get("type")
title = content.get("title")
description = content.get("description")
success = content.get("success", True)

self.textarea.insert("end", "\n", "log_text")
self.textarea.insert("end", f" {time} ", "timestamp")

if success:
self.textarea.insert("end", f" {type_name.upper()} ", "prefix_sniper_success")
else:
self.textarea.insert("end", f" {type_name.upper()} ", "prefix_sniper_fail")

self.textarea.insert("end", f"\n[{time}] ", "timestamp")
self.textarea.insert("end", f"[{type.upper()}] ", f"prefix_{prefix.lower()}")
self.textarea.insert("end", f"{title}\n", "log_text")

for key, value in description.items():
self.textarea.insert("end", f"{' ' * len(f'[{time}] ')}{key}: ", "sniper_key")
self.textarea.insert("end", f" {key}: ", "sniper_key")
self.textarea.insert("end", f"{value}\n", "log_text")

self.textarea.insert("end", "\n", "log_text")
else:
self.textarea.insert("end", f"[{time}] ", "timestamp")
self.textarea.insert("end", f"[{prefix}] ", f"prefix_{prefix.lower()}")
self.textarea.insert("end", f"{text}\n", "log_text")

self.textarea.yview_moveto(1)
except:
print("Console tried to update without being drawn.")
pass

def clear(self):
self.console = []
try:
self.textarea.delete("1.0", "end")
except:
print("Console tried to clear without being drawn.")
pass

def add_sniper(self, sniper_obj):
self.add_log("sniper", sniper_obj)
self._notify_home_feed(sniper_obj)

def _notify_home_feed(self, sniper_obj):
try:
from gui.pages.home import HomePage
gui = self.bot.gui if hasattr(self.bot, 'gui') else None
if gui and hasattr(gui, 'home_page'):
event_type = sniper_obj.get("type", "sniper")
title = sniper_obj.get("title", "Sniper Event")
desc_parts = sniper_obj.get("description", {})
description = "\n".join(f"{k}: {v}" for k, v in desc_parts.items())
success = "success" not in title.lower() or "failed" not in title.lower()
gui.home_page._add_notification(
f"{event_type.lower()}_snipe",
title,
description,
color="#4fee4c" if success else "#ff6464"
)
except Exception:
pass

def add_log(self, prefix, text):
time = get_formatted_time()
self.console.append((time, prefix, text))
self.update()

def _load_tags(self):
self.textarea.tag_config("timestamp", foreground=Style.DARK_GREY.value, font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("log_text", foreground=Style.LIGHT_GREY.value, font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
font_size = self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size
mono_font = ("JetBrainsMono NF", font_size, "bold")

self.textarea.tag_config("timestamp", foreground=Style.DARK_GREY.value, font=mono_font)
self.textarea.tag_config("log_text", foreground=Style.LIGHT_GREY.value, font=mono_font)

self.textarea.tag_config("prefix_sniper", foreground="red", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("sniper_key", foreground="#eceb18", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_command", foreground="#0b91ff", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_info", foreground="#2aefef", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_success", foreground="#4fee4c", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_warning", foreground="#eceb18", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_error", foreground="red", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_cli", foreground="pink", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_rpc", foreground="pink", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_captcha", foreground="#eceb18", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_nitro", foreground="#0b91ff", font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold"))
self.textarea.tag_config("prefix_sniper", foreground="red", font=mono_font)
self.textarea.tag_config("prefix_sniper_success", foreground="#4fee4c", font=mono_font)
self.textarea.tag_config("prefix_sniper_fail", foreground="#ff6464", font=mono_font)
self.textarea.tag_config("sniper_key", foreground="#eceb18", font=mono_font)
self.textarea.tag_config("prefix_command", foreground="#0b91ff", font=mono_font)
self.textarea.tag_config("prefix_info", foreground="#2aefef", font=mono_font)
self.textarea.tag_config("prefix_success", foreground="#4fee4c", font=mono_font)
self.textarea.tag_config("prefix_warning", foreground="#eceb18", font=mono_font)
self.textarea.tag_config("prefix_error", foreground="red", font=mono_font)
self.textarea.tag_config("prefix_cli", foreground="pink", font=mono_font)
self.textarea.tag_config("prefix_rpc", foreground="pink", font=mono_font)
self.textarea.tag_config("prefix_captcha", foreground="#eceb18", font=mono_font)
self.textarea.tag_config("prefix_nitro", foreground="#0b91ff", font=mono_font)

def _draw_footer(self, parent):
""" Draw the footer with user info and clear button. """
Expand All @@ -102,7 +138,7 @@ def _draw_footer(self, parent):

def _draw_main(self, parent):
wrapper = RoundedFrame(parent, radius=15, bootstyle="dark.TFrame")
wrapper.pack(side="top", fill="both", expand=True)
wrapper.pack(fill="both", expand=True)

self.textarea = ttk.Text(wrapper, wrap="word", height=20,
font=("JetBrainsMono NF", self.non_darwin_font_size if sys.platform != "darwin" else self.darwin_font_size, "bold")
Expand All @@ -126,10 +162,12 @@ def _draw_main(self, parent):

self.textarea.pack(fill="both", expand=True, padx=5, pady=5)
self._load_tags()
self._wrapper = wrapper

def draw(self, parent):
self.avatar = self.bot.get_avatar(size=15, radius=2)
self._draw_main(parent)
return self._wrapper
# if self.bot.running:
# self._draw_footer(parent)
self.update()
154 changes: 154 additions & 0 deletions gui/components/notification_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import sys
import time
import ttkbootstrap as ttk

from gui.helpers.style import Style


class Toast:
def __init__(self, parent, root, event_type, title, description="", timestamp=None, color=None, on_dismiss=None):
from utils.config import Config
self.parent = parent
self.root = root
self.on_dismiss = on_dismiss
self.color = color or self._get_color(event_type)
self.timestamp = timestamp or time.strftime("%H:%M:%S")

timeout_sec = Config().get("notification_timeout")
self.AUTO_DISMISS_MS = (timeout_sec if isinstance(timeout_sec, (int, float)) and timeout_sec > 0 else 5) * 1000

self.frame = ttk.Frame(parent, style="dark.TFrame")
self.frame.pack(fill=ttk.X, padx=4, pady=(0, 4))

card = ttk.Frame(self.frame, style="dark.TFrame")
card.pack(fill=ttk.X)

accent = ttk.Canvas(card, width=3, height=3, highlightthickness=0,
background=self.root.style.colors.get("dark"))
accent.pack(side=ttk.LEFT, fill="y", padx=(0, 8))
accent.create_rectangle(0, 0, 3, 500, fill=self.color, outline=self.color)

content = ttk.Frame(card, style="dark.TFrame")
content.pack(side=ttk.LEFT, fill=ttk.X, expand=True, pady=6)

top = ttk.Frame(content, style="dark.TFrame")
top.pack(fill=ttk.X)

type_lbl = ttk.Label(top, text=event_type.replace("_", " ").title(),
font=("Host Grotesk", 9, "bold"), foreground=self.color)
type_lbl.configure(background=self.root.style.colors.get("dark"))
type_lbl.pack(side=ttk.LEFT)

time_lbl = ttk.Label(top, text=self.timestamp, font=("Host Grotesk", 8),
foreground=Style.DARK_GREY.value)
time_lbl.configure(background=self.root.style.colors.get("dark"))
time_lbl.pack(side=ttk.RIGHT)

title_lbl = ttk.Label(content, text=title, font=("Host Grotesk", 10, "bold"))
title_lbl.configure(background=self.root.style.colors.get("dark"))
title_lbl.pack(anchor=ttk.W)

if description:
desc_lbl = ttk.Label(content, text=description, font=("Host Grotesk", 9),
foreground=Style.LIGHT_GREY.value, wraplength=400)
desc_lbl.configure(background=self.root.style.colors.get("dark"))
desc_lbl.pack(anchor=ttk.W)

dismiss = ttk.Label(card, text="\u00d7", font=("Host Grotesk", 14, "bold"),
cursor="hand2", foreground=Style.DARK_GREY.value)
dismiss.configure(background=self.root.style.colors.get("dark"))
dismiss.pack(side=ttk.RIGHT, padx=8, anchor=ttk.N)
dismiss.bind("<Button-1>", lambda e: self.dismiss())
dismiss.bind("<Enter>", lambda e: dismiss.configure(foreground="white"))
dismiss.bind("<Leave>", lambda e: dismiss.configure(foreground=Style.DARK_GREY.value))

self._timer = self.root.after(self.AUTO_DISMISS_MS, self.dismiss)

def _get_color(self, event_type):
colors = {
"friend_add": "#4fee4c", "friend_remove": "#ff6464",
"relationship_add": "#4fee4c", "relationship_remove": "#ff6464",
"relationship_update": "#eceb18", "guild_remove": "#ff6464",
"guild_join": "#4fee4c", "nitro_snipe": "#0b91ff",
"privnote_snipe": "#a07cff", "info": "#2aefef",
"warning": "#eceb18", "error": "#ff6464", "success": "#4fee4c",
}
return colors.get(event_type, Style.LIGHT_GREY.value)

def dismiss(self):
try:
if self._timer:
self.root.after_cancel(self._timer)
except Exception:
pass
try:
if self.frame and self.frame.winfo_exists():
self.frame.destroy()
except Exception:
pass
if self.on_dismiss:
self.on_dismiss(self)


class NotificationFeed:
MAX_VISIBLE = 5

def __init__(self, root):
self.root = root
self.wrapper = None
self.toasts = []
self.history = []
self._logger_page = None

def set_logger(self, logger_page):
self._logger_page = logger_page

def draw(self, parent):
self.wrapper = ttk.Frame(parent, style="dark.TFrame")
return self.wrapper

def add_notification(self, event_type, title, description="", timestamp=None, color=None):
ts = timestamp or time.strftime("%H:%M:%S")
clr = color or self._get_color(event_type)

self.history.append({
"event_type": event_type,
"title": title,
"description": description,
"timestamp": ts,
"color": clr,
})

if self._logger_page:
self._logger_page.add_entry(event_type, title, description, timestamp=ts, color=clr)

if not self.wrapper or not self.wrapper.winfo_exists():
return

toast = Toast(self.wrapper, self.root, event_type, title, description,
timestamp=ts, color=clr, on_dismiss=self._on_dismiss)
self.toasts.append(toast)

while len(self.toasts) > self.MAX_VISIBLE:
old = self.toasts.pop(0)
old.dismiss()

def _get_color(self, event_type):
colors = {
"friend_add": "#4fee4c", "friend_remove": "#ff6464",
"relationship_add": "#4fee4c", "relationship_remove": "#ff6464",
"relationship_update": "#eceb18", "guild_remove": "#ff6464",
"guild_join": "#4fee4c", "nitro_snipe": "#0b91ff",
"privnote_snipe": "#a07cff", "info": "#2aefef",
"warning": "#eceb18", "error": "#ff6464", "success": "#4fee4c",
}
return colors.get(event_type, Style.LIGHT_GREY.value)

def _on_dismiss(self, toast):
if toast in self.toasts:
self.toasts.remove(toast)

def clear(self):
for t in self.toasts[:]:
t.dismiss()
self.toasts.clear()
32 changes: 32 additions & 0 deletions gui/components/settings/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import utils.console as console
from gui.components import SettingsPanel, DropdownMenu, RoundedButton, RoundedSwitch
from gui.helpers import apply_theme, get_themes
from utils.files import set_launch_on_startup

class GeneralPanel(SettingsPanel):
def __init__(self, root, parent, bot_controller, images, config, draw_settings, width=None):
Expand Down Expand Up @@ -49,6 +50,21 @@ def _save_cfg(self):
self.cfg.set("telemetry", self.telemetry_entry.instate(["selected"]), save=False)
except Exception as e:
console.error(f"Failed to set telemetry setting: {e}")

try:
startup_enabled = self.startup_entry.instate(["selected"])
self.cfg.set("launch_on_startup", startup_enabled, save=False)
success, err = set_launch_on_startup(startup_enabled)
if not success:
console.warning(f"Could not update startup setting: {err}")
except Exception as e:
console.error(f"Failed to set startup setting: {e}")

try:
timeout_val = int(self.notif_timeout_entry.value())
self.cfg.set("notification_timeout", timeout_val, save=False)
except Exception as e:
console.error(f"Failed to set notification timeout: {e}")

self.cfg.save(notify=False)

Expand Down Expand Up @@ -131,6 +147,22 @@ def draw(self):
self.telemetry_entry.configure(variable=ttk.BooleanVar(value=self.cfg.get("telemetry")))
self.telemetry_entry.grid(row=1, column=1, sticky=ttk.E, padx=(10, 10), pady=(2, 10))

startup_label = ttk.Label(checkboxes_frame, text="Launch Ghost on startup")
startup_label.configure(background=self.root.style.colors.get("dark"))
startup_label.grid(row=2, column=0, sticky=ttk.NW, padx=(10, 0), pady=(2, 10))

self.startup_entry = RoundedSwitch(checkboxes_frame, command=self._save_cfg, parent_background=self.root.style.colors.get("dark"))
self.startup_entry.configure(variable=ttk.BooleanVar(value=self.cfg.get("launch_on_startup")))
self.startup_entry.grid(row=2, column=1, sticky=ttk.E, padx=(10, 10), pady=(2, 10))

notif_timeout_label = ttk.Label(self.body, text="Notification timeout (seconds)")
notif_timeout_label.configure(background=self.root.style.colors.get("dark"))
notif_timeout_label.grid(row=len(self.config_entries) + 4, column=0, sticky=ttk.NW, padx=(10, 0), pady=(5, 10))

self.notif_timeout_entry = DropdownMenu(self.body, options=["3", "5", "10", "15", "30", "60"], command=lambda _: self._save_cfg())
self.notif_timeout_entry.set_selected(str(self.cfg.get("notification_timeout")))
self.notif_timeout_entry.draw().grid(row=len(self.config_entries) + 4, column=1, sticky="we", padx=(10, 10), pady=(2, 10), columnspan=3)

return self.wrapper

def save(self):
Expand Down
Loading