feat: initial commit

This commit is contained in:
2026-06-15 13:39:41 +07:00
commit 0afbecbe83
32 changed files with 6620 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
import { app, shell, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron'
import { join } from 'path'
import * as fs from 'fs'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import { relay, RelayStatus } from './relay'
function settingsPath(): string {
return join(app.getPath('userData'), 'settings.json')
}
function loadDisabledMirrors(): number[] {
try {
const raw = fs.readFileSync(settingsPath(), 'utf8')
const data = JSON.parse(raw)
return Array.isArray(data?.disabledMirrors) ? data.disabledMirrors : []
} catch {
return []
}
}
function saveDisabledMirrors(indices: number[]): void {
fs.writeFileSync(settingsPath(), JSON.stringify({ disabledMirrors: indices }), 'utf8')
}
let mainWindow: BrowserWindow | null = null
let tray: Tray | null = null
let quitting = false
function createWindow(): void {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
return
}
mainWindow = new BrowserWindow({
width: 420,
height: 760,
show: false,
autoHideMenuBar: true,
resizable: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
})
mainWindow.on('close', (e) => {
if (!quitting) {
e.preventDefault()
mainWindow?.destroy()
}
})
mainWindow.on('closed', () => {
mainWindow = null
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
function createTray(): void {
const trayIcon = nativeImage.createFromPath(icon).resize({ width: 24, height: 24 })
tray = new Tray(trayIcon)
tray.setToolTip('Discord RPC Relay')
updateTrayMenu(relay.getStatus())
tray.on('click', () => {
createWindow()
})
}
function updateTrayMenu(status: RelayStatus): void {
if (!tray) return
const menu = Menu.buildFromTemplate([
{
label: status.running ? 'Relay: Running' : 'Relay: Stopped',
enabled: false
},
{ type: 'separator' },
{
label: status.running ? 'Stop Relay' : 'Start Relay',
click: async () => {
try {
if (status.running) {
await relay.stop()
} else {
await relay.start()
}
} catch (err) {
console.error(err)
}
}
},
{
label: 'Show Window',
click: () => createWindow()
},
{ type: 'separator' },
{
label: 'Quit',
click: () => {
quitting = true
app.quit()
}
}
])
tray.setContextMenu(menu)
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('cafe.kirameki.discord-rpc-relay')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
relay.setDisabledMirrors(loadDisabledMirrors())
ipcMain.handle('relay:get-status', () => relay.getStatus())
ipcMain.handle('relay:start', () => relay.start())
ipcMain.handle('relay:stop', () => relay.stop())
ipcMain.handle('relay:get-autostart', () => app.getLoginItemSettings().openAtLogin)
ipcMain.handle('relay:set-autostart', (_e, enabled: boolean) => {
app.setLoginItemSettings({ openAtLogin: enabled })
return app.getLoginItemSettings().openAtLogin
})
ipcMain.handle('relay:set-mirror-enabled', (_e, index: number, enabled: boolean) => {
const status = relay.setMirrorEnabled(index, enabled)
saveDisabledMirrors(relay.getDisabledMirrors())
return status
})
relay.on('status', (status: RelayStatus) => {
updateTrayMenu(status)
mainWindow?.webContents.send('relay:status', status)
})
createTray()
createWindow()
relay.start().catch((err) => console.error('Failed to start relay:', err))
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
// Keep running in the tray
})
let stopping = false
app.on('before-quit', (e) => {
quitting = true
if (stopping) return
stopping = true
e.preventDefault()
relay.stop().finally(() => app.exit(0))
})