Skip to content

Commit 00659fd

Browse files
authored
Merge pull request #18 from Reallukeisbest/main
Add a settings panel, reworked how plugins are loaded for more modularity, an Apple emoji plugin, command+r support, file picker support, custom css, live css reload, and more.
2 parents d380837 + f81a556 commit 00659fd

File tree

13 files changed

+671
-228
lines changed

13 files changed

+671
-228
lines changed

Discord/DiscordApp.swift

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import SwiftUI
99
import AppKit
10+
import Foundation
1011

1112
class WindowDelegate: NSObject, NSWindowDelegate {
1213
func windowDidResize(_ notification: Notification) {
@@ -56,62 +57,111 @@ class WindowDelegate: NSObject, NSWindowDelegate {
5657
@main
5758
struct DiscordApp: App {
5859
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
59-
@AppStorage("FakeNitro") var fakeNitro: Bool = false
60+
61+
func extractMetadata(from plugin: String) -> ([String: String]) {
62+
var metadata = [String: String]()
63+
64+
let lines = plugin.components(separatedBy: "\n")
65+
66+
for line in lines {
67+
if line.contains ("==/VoxaPlugin==") {
68+
return metadata
69+
}
70+
71+
if line.trimmingCharacters(in: .whitespaces).starts(with: "// @") {
72+
let cleanedLine = line.replacingOccurrences(of: "// @", with: "").trimmingCharacters(in: .whitespaces)
73+
if let separatorIndex = cleanedLine.firstIndex(of: ":") {
74+
let key = String(cleanedLine[..<separatorIndex]).trimmingCharacters(in: .whitespaces)
75+
let value = String(cleanedLine[cleanedLine.index(after: separatorIndex)...]).trimmingCharacters(in: .whitespaces)
76+
metadata[key] = value
77+
}
78+
}
79+
}
80+
81+
return metadata
82+
}
83+
84+
init() {
85+
if let resourcePath = Bundle.main.resourcePath {
86+
let fileManager = FileManager.default
87+
88+
do {
89+
let files = try fileManager.contentsOfDirectory(atPath: resourcePath)
90+
let possiblePluginFiles = files.filter { $0.hasSuffix(".js") }
91+
92+
for file in possiblePluginFiles {
93+
// Build the full file path
94+
let filePath = (resourcePath as NSString).appendingPathComponent(file)
95+
96+
// Check if it's a file (not a directory)
97+
var isDir: ObjCBool = false
98+
if fileManager.fileExists(atPath: filePath, isDirectory: &isDir), !isDir.boolValue {
99+
do {
100+
let script = try String(contentsOfFile: filePath, encoding: .utf8)
101+
var metadata = extractMetadata(from: script)
102+
let pathWithoutExtension = (file as NSString).deletingPathExtension
103+
let id = pathWithoutExtension.lowercased()
104+
105+
metadata["pathWithoutExtension"] = pathWithoutExtension
106+
107+
Vars.plugins[id] = metadata
108+
} catch {
109+
print("Couldn't load plugin \(filePath): \(error.localizedDescription)")
110+
}
111+
}
112+
}
113+
} catch {
114+
print("Error reading files from Bundle: \(error.localizedDescription)")
115+
}
116+
} else {
117+
print("Could not find the resource path in Bundle.main.")
118+
}
119+
}
60120

61121
var body: some Scene {
62122
WindowGroup {
63123
ContentView()
64124
.onAppear {
65125
// Use a guard to ensure there's a main screen
66-
guard let mainScreen = NSScreen.main else {
126+
if (NSScreen.main == nil) {
67127
print("No available main screen to set initial window frame.")
68128
return
69129
}
70130

71131
// If there's a main application window, configure it
72132
if let window = NSApplication.shared.windows.first {
73-
let screenFrame = mainScreen.visibleFrame
74-
let newWidth: CGFloat = 1000
75-
let newHeight: CGFloat = 600
76-
77-
// Center the window
78-
let centeredX = screenFrame.midX - (newWidth / 2)
79-
let centeredY = screenFrame.midY - (newHeight / 2)
80-
81-
let initialFrame = NSRect(x: centeredX,
82-
y: centeredY,
83-
width: newWidth,
84-
height: newHeight)
85-
86-
window.setFrame(initialFrame, display: true)
87-
88-
// Configure window for resizing
89-
window.styleMask.insert(.resizable)
90-
91-
// Set min/max sizes
92-
window.minSize = NSSize(width: 600, height: 400)
93-
window.maxSize = NSSize(width: 2000, height: screenFrame.height)
94-
95-
// Disable frame autosaving
96-
window.setFrameAutosaveName("")
97-
98-
// Assign delegate for traffic light positioning
99-
window.delegate = appDelegate.windowDelegate
133+
// Get the visible frame of the main screen
134+
let screenFrame = NSScreen.main?.visibleFrame ?? .zero
135+
136+
// Set the window frame to match the screen's visible frame
137+
window.setFrame(screenFrame, display: true)
138+
139+
// Configure window for resizing
140+
window.styleMask.insert(.resizable)
141+
142+
// Optionally, set min/max sizes if needed
143+
window.minSize = NSSize(width: 600, height: 400)
144+
145+
// Disable frame autosaving
146+
window.setFrameAutosaveName("")
147+
148+
// Assign delegate for traffic light positioning
149+
window.delegate = appDelegate.windowDelegate
100150
}
101151
}
102152
}
103153
.windowStyle(.hiddenTitleBar)
104-
.commands {
105-
CommandGroup(replacing: .windowArrangement) { }
106-
107-
CommandMenu("Plugins") {
108-
Button {
109-
fakeNitro.toggle()
110-
} label: {
111-
Text("\(fakeNitro ? "Disable" : "Enable") Fake Nitro")
112-
}
113-
114-
}
154+
.commands {
155+
CommandGroup(replacing: .newItem) {
156+
Button("Reload") {
157+
hardReloadWebView(webView: Vars.webViewReference!)
158+
}
159+
.keyboardShortcut("r", modifiers: .command)
160+
}
161+
}
162+
163+
Settings {
164+
SettingsView()
115165
}
116166
}
117167
}

Discord/DiscordWindowContent.swift

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import WebKit
33

44
struct DiscordWindowContent: View {
55
var channelClickWidth: CGFloat
6-
var initialURL: String = "https://discord.com/channels/@me"
7-
var customCSS: String?
8-
@AppStorage("FakeNitro") var fakeNitro: Bool = false
6+
var initialURL: String = "https://discord.com/app"
97

108
// Reference to the underlying WKWebView
119
@State var webViewReference: WKWebView?
@@ -20,16 +18,10 @@ struct DiscordWindowContent: View {
2018
// Embed the Discord WebView
2119
WebView(channelClickWidth: channelClickWidth,
2220
initialURL: initialURL,
23-
customCSS: customCSS,
2421
webViewReference: $webViewReference)
2522
.frame(maxWidth: .infinity, maxHeight: .infinity)
26-
.onChange(of: fakeNitro) {
27-
guard let webView = webViewReference else { return }
28-
if fakeNitro {
29-
enableFakeNitro(webView)
30-
} else {
31-
disableFakeNitro(webView)
32-
}
23+
.onChange(of: webViewReference) {
24+
Vars.webViewReference = webViewReference
3325
}
3426
}
3527

@@ -46,18 +38,6 @@ struct DiscordWindowContent: View {
4638
}
4739
}
4840

49-
func disableFakeNitro(_ webView: WKWebView) {
50-
let script = "disableFNitro();"
51-
webView.reload()
52-
webView.configuration.userContentController.addUserScript(WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true))
53-
}
54-
55-
func enableFakeNitro(_ webView: WKWebView) {
56-
let script = "enableFNitro();"
57-
webView.reload()
58-
webView.configuration.userContentController.addUserScript(WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true))
59-
}
60-
6141
#Preview {
6242
DiscordWindowContent(channelClickWidth: 1000)
6343
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// ==VoxaPlugin==
2+
// @name: Apple Emojis
3+
// @author: DevilBro
4+
// @description: Replaces Discord's Emojis with Apple's Emojis.
5+
// @url: https://github.com/mwittrien/BetterDiscordAddons/tree/master/Themes/EmojiReplace
6+
// ==/VoxaPlugin==
7+
8+
const emojiStyle = document.createElement('style');
9+
emojiStyle.textContent = `@import url(https://mwittrien.github.io/BetterDiscordAddons/Themes/EmojiReplace/base/Apple.css)`;
10+
document.head.appendChild(emojiStyle);
Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
//
2-
// FakeNitro.js
3-
// Discord
4-
//
5-
// Created by Stossy11 on 06/01/2025.
6-
//
1+
// ==VoxaPlugin==
2+
// @name: FakeNitro
3+
// @author: Stossy11
4+
// @description: Simulates Nitro.
5+
// ==/VoxaPlugin==
76

87
let z;
9-
let isEnabled = false; // Flag to control the script's execution
8+
let isEnabled = true; // Flag to control the script's execution
109

1110
function loader() {
1211
if (!isEnabled) {
@@ -113,11 +112,3 @@ function loader() {
113112
}
114113

115114
z = setInterval(loader, 1);
116-
117-
function enableFNitro() {
118-
isEnabled = true;
119-
}
120-
121-
function disableFNitro() {
122-
isEnabled = false;
123-
}

Discord/SecondaryWindowController.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class SecondaryWindowController: NSWindowController {
137137
"""
138138

139139
// Create the SwiftUI view for the window with custom CSS
140-
let contentView = SecondaryWindowView(url: url, channelClickWidth: channelClickWidth, customCSS: secondaryCSS)
140+
let contentView = SecondaryWindowView(url: url, channelClickWidth: channelClickWidth)
141141
window.contentView = NSHostingView(rootView: contentView)
142142

143143
self.init(window: window)
@@ -157,12 +157,10 @@ class SecondaryWindowController: NSWindowController {
157157
struct SecondaryWindowView: View {
158158
let url: String
159159
let channelClickWidth: CGFloat
160-
let customCSS: String
161160

162161
var body: some View {
163162
DiscordWindowContent(channelClickWidth: channelClickWidth,
164-
initialURL: url,
165-
customCSS: customCSS)
163+
initialURL: url)
166164
.frame(minWidth: 200, minHeight: 200)
167165
}
168166
}
@@ -173,7 +171,7 @@ struct SecondaryWindowScene: Scene {
173171

174172
var body: some Scene {
175173
WindowGroup {
176-
SecondaryWindowView(url: url, channelClickWidth: channelClickWidth, customCSS: "")
174+
SecondaryWindowView(url: url, channelClickWidth: channelClickWidth)
177175
}
178176
.windowStyle(.hiddenTitleBar)
179177
.windowResizability(.contentSize)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import SwiftUI
2+
import WebKit
3+
4+
struct CustomCssView: View {
5+
@AppStorage("customCSS") private var customCSS: String = """
6+
:root {
7+
--background-accent: rgb(0, 0, 0, 0.5) !important;
8+
--background-floating: transparent !important;
9+
--background-message-highlight: transparent !important;
10+
--background-message-highlight-hover: transparent !important;
11+
--background-message-hover: transparent !important;
12+
--background-mobile-primary: transparent !important;
13+
--background-mobile-secondary: transparent !important;
14+
--background-modifier-accent: transparent !important;
15+
--background-modifier-active: transparent !important;
16+
--background-modifier-hover: transparent !important;
17+
--background-modifier-selected: transparent !important;
18+
--background-nested-floating: transparent !important;
19+
--background-primary: transparent !important;
20+
--background-secondary: transparent !important;
21+
--background-secondary-alt: transparent !important;
22+
--background-tertiary: transparent !important;
23+
--bg-overlay-3: transparent !important;
24+
--channeltextarea-background: transparent !important;
25+
}
26+
27+
.sidebar_a4d4d9 {
28+
background-color: rgb(0, 0, 0, 0.15) !important;
29+
border-right: solid 1px rgb(0, 0, 0, 0.3) !important;
30+
}
31+
32+
.guilds_a4d4d9 {
33+
background-color: rgb(0, 0, 0, 0.3) !important;
34+
border-right: solid 1px rgb(0, 0, 0, 0.3) !important;
35+
padding-top: 48px;
36+
}
37+
38+
.theme-dark .themed_fc4f04 {
39+
background-color: transparent !important;
40+
}
41+
42+
.channelTextArea_a7d72e {
43+
background-color: rgb(0, 0, 0, 0.15) !important;
44+
}
45+
46+
.button_df39bd {
47+
background-color: rgb(0, 0, 0, 0.15) !important;
48+
}
49+
50+
.chatContent_a7d72e {
51+
background-color: transparent !important;
52+
background: transparent !important;
53+
}
54+
55+
.chat_a7d72e {
56+
background: transparent !important;
57+
}
58+
59+
.content_a7d72e {
60+
background: none !important;
61+
}
62+
63+
.container_eedf95 {
64+
position: relative;
65+
background-color: rgba(0, 0, 0, 0.5);
66+
}
67+
68+
.container_eedf95::before {
69+
content: '';
70+
position: absolute;
71+
top: 0;
72+
left: 0;
73+
right: 0;
74+
bottom: 0;
75+
backdrop-filter: none;
76+
filter: blur(10px);
77+
background-color: inherit;
78+
z-index: -1;
79+
}
80+
81+
.container_a6d69a {
82+
background: transparent !important;
83+
background-color: transparent !important;
84+
backdrop-filter: blur(10px); !important;
85+
}
86+
87+
.mainCard_a6d69a {
88+
background-color: rgb(0, 0, 0, 0.15) !important;
89+
}
90+
"""
91+
92+
var body: some View {
93+
TextEditor(text: $customCSS)
94+
.padding()
95+
.onChange(of: customCSS) {
96+
Vars.webViewReference!.evaluateJavaScript("document.getElementById('voxastyle').textContent = `\(customCSS)`;")
97+
}
98+
}
99+
}
100+

0 commit comments

Comments
 (0)