Add files via upload

This commit is contained in:
Savsis 2026-04-26 15:18:06 +05:00 • committed by GitHub
parent 74b2f56dae
commit f9524c2691
10 changed files with 6753 additions and 0 deletions

254
burmalda-music/main.js Normal file
View file

@ -0,0 +1,254 @@
const {
app, BrowserWindow, ipcMain, globalShortcut, shell, dialog,
protocol, session, net,
} = require('electron');
const path = require('path');
const fs = require('fs');
const os = require('os');
const crypto = require('crypto');
const { autoUpdater } = require('electron-updater');
// ── DRM cache scheme — must be registered before app.whenReady() ──────────────
protocol.registerSchemesAsPrivileged([{
scheme: 'bmcache',
privileges: {
standard: true,
secure: true,
bypassCSP: true,
corsEnabled: true,
stream: true,
supportFetchAPI: true,
},
}]);
// ── Persistent store ──────────────────────────────────────────────────────────
let Store;
async function getStore() {
if (!Store) {
const mod = await import('electron-store');
Store = mod.default;
}
return new Store({ name: 'burmalda' });
}
// ── DRM cache helpers ─────────────────────────────────────────────────────────
const CACHE_DIR = path.join(os.homedir(), '.burmalda-cache');
async function getCacheKey() {
const store = await getStore();
let hex = store.get('cacheKey');
if (!hex) {
hex = crypto.randomBytes(32).toString('hex');
store.set('cacheKey', hex);
}
return Buffer.from(hex, 'hex');
}
function cacheFilePath(trackId) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
return path.join(CACHE_DIR, `${trackId}.bmc`);
}
async function readCached(trackId) {
const fp = cacheFilePath(trackId);
if (!fs.existsSync(fp)) return null;
try {
const enc = fs.readFileSync(fp);
const key = await getCacheKey();
const iv = enc.slice(0, 16);
const body = enc.slice(16);
const dec = crypto.createDecipheriv('aes-256-cbc', key, iv);
const plain = Buffer.concat([dec.update(body), dec.final()]);
const mimeLen = plain.readUInt32BE(0);
const mime = plain.slice(4, 4 + mimeLen).toString('utf8');
const audio = plain.slice(4 + mimeLen);
return { audio, mime };
} catch {
try { fs.unlinkSync(fp); } catch {}
return null;
}
}
const _downloading = new Set();
async function downloadAndCache(url, trackId) {
if (_downloading.has(trackId)) return;
const fp = cacheFilePath(trackId);
if (fs.existsSync(fp)) return;
_downloading.add(trackId);
try {
const resp = await net.fetch(url);
if (!resp.ok) return;
const mime = resp.headers.get('content-type') || 'audio/mpeg';
const audio = Buffer.from(await resp.arrayBuffer());
const key = await getCacheKey();
const iv = crypto.randomBytes(16);
const mimeBuf = Buffer.from(mime, 'utf8');
const lenBuf = Buffer.allocUnsafe(4);
lenBuf.writeUInt32BE(mimeBuf.length, 0);
const plain = Buffer.concat([lenBuf, mimeBuf, audio]);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const enc = Buffer.concat([iv, cipher.update(plain), cipher.final()]);
fs.writeFileSync(fp, enc);
} catch {
// network errors silently ignored
} finally {
_downloading.delete(trackId);
}
}
// Extract a stable track-id from CDN URL (filename without extension)
function trackIdFromUrl(url) {
return path.basename(new URL(url).pathname).replace(/\.[^.]+$/, '');
}
// ── DRM cache interception ────────────────────────────────────────────────────
function setupDrmCache() {
// 1. Serve decrypted audio via bmcache:// scheme
protocol.handle('bmcache', async (request) => {
const trackId = decodeURIComponent(new URL(request.url).pathname.replace('/audio/', ''));
const cached = await readCached(trackId);
if (cached) {
return new Response(cached.audio, {
status: 200,
headers: {
'content-type': cached.mime,
'content-length': String(cached.audio.length),
'accept-ranges': 'bytes',
'access-control-allow-origin': '*',
},
});
}
return new Response('not cached', { status: 404 });
});
// 2. Intercept CDN audio-only requests → redirect to local cache when available
// Cover images (.jpg) and other non-audio assets pass through untouched.
const AUDIO_EXTS = new Set(['.mp3', '.m4a', '.ogg', '.wav', '.flac', '.aac', '.opus', '.webm', '.weba']);
const CDN_PATTERNS = [
'https://cdn.nodeshift.space/music/*',
'https://cdnl.nodeshift.space/music/*',
];
session.defaultSession.webRequest.onBeforeRequest({ urls: CDN_PATTERNS }, (details, cb) => {
const url = details.url;
const ext = path.extname(new URL(url).pathname).toLowerCase();
if (!AUDIO_EXTS.has(ext)) { cb({}); return; }
const trackId = trackIdFromUrl(url);
const fp = cacheFilePath(trackId);
if (fs.existsSync(fp)) {
cb({ redirectURL: `bmcache://audio/${encodeURIComponent(trackId)}` });
} else {
downloadAndCache(url, trackId); // cache in background
cb({}); // pass through for now
}
});
}
// ── IPC: cache management (exposed to renderer via contextBridge) ─────────────
ipcMain.handle('cache-size', async () => {
try {
const files = fs.readdirSync(CACHE_DIR).filter(f => f.endsWith('.bmc'));
const bytes = files.reduce((s, f) => s + fs.statSync(path.join(CACHE_DIR, f)).size, 0);
return { count: files.length, bytes };
} catch { return { count: 0, bytes: 0 }; }
});
ipcMain.handle('cache-clear', async () => {
try {
const files = fs.readdirSync(CACHE_DIR).filter(f => f.endsWith('.bmc'));
files.forEach(f => { try { fs.unlinkSync(path.join(CACHE_DIR, f)); } catch {} });
return files.length;
} catch { return 0; }
});
// ── Main window ───────────────────────────────────────────────────────────────
const MUSIC_URL = 'https://music.nodeshift.space';
let mainWindow = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1280, height: 800, minWidth: 900, minHeight: 600,
frame: false,
titleBarStyle: 'hidden',
titleBarOverlay: { color: '#0a0a0a', symbolColor: '#ffffff', height: 38 },
backgroundColor: '#0a0a0a',
show: false,
icon: path.join(__dirname, 'src/assets/icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
webSecurity: true,
allowRunningInsecureContent: false,
},
});
mainWindow.loadURL(MUSIC_URL);
mainWindow.once('ready-to-show', () => { mainWindow.show(); mainWindow.focus(); });
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
mainWindow.webContents.on('will-navigate', (_event, url) => {
if (!url.includes('nodeshift.space') && !url.startsWith('https://accounts.google.com')) {
_event.preventDefault();
shell.openExternal(url);
}
});
}
// ── Auto-update ───────────────────────────────────────────────────────────────
function setupAutoUpdater() {
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox(mainWindow, {
type: 'info', title: 'Обновление готово',
message: 'Новая версия Burmalda Music скачана. Перезапустить сейчас?',
buttons: ['Перезапустить', 'Позже'], defaultId: 0,
}).then(({ response }) => { if (response === 0) autoUpdater.quitAndInstall(); });
});
setTimeout(() => autoUpdater.checkForUpdates().catch(() => {}), 3000);
setInterval(() => autoUpdater.checkForUpdates().catch(() => {}), 4 * 60 * 60 * 1000);
}
// ── App lifecycle ─────────────────────────────────────────────────────────────
app.whenReady().then(() => {
setupDrmCache();
createWindow();
setupMediaKeys();
setupAutoUpdater();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
app.on('will-quit', () => globalShortcut.unregisterAll());
// ── Window controls ───────────────────────────────────────────────────────────
ipcMain.on('win-minimize', () => mainWindow?.minimize());
ipcMain.on('win-maximize', () => { if (mainWindow?.isMaximized()) mainWindow.unmaximize(); else mainWindow?.maximize(); });
ipcMain.on('win-close', () => mainWindow?.close());
ipcMain.on('win-fullscreen', () => mainWindow?.setFullScreen(!mainWindow.isFullScreen()));
// ── Persistent store IPC ──────────────────────────────────────────────────────
ipcMain.handle('store-get', async (_, key) => { const s = await getStore(); return s.get(key); });
ipcMain.handle('store-set', async (_, key, value) => { const s = await getStore(); s.set(key, value); });
ipcMain.handle('store-delete', async (_, key) => { const s = await getStore(); s.delete(key); });
ipcMain.handle('get-version', () => app.getVersion());
// ── Media keys ────────────────────────────────────────────────────────────────
function execSafe(js) { mainWindow?.webContents.executeJavaScript(js).catch(() => {}); }
function setupMediaKeys() {
globalShortcut.register('MediaPlayPause', () => execSafe('typeof togglePlay==="function"&&togglePlay()'));
globalShortcut.register('MediaNextTrack', () => execSafe('typeof next==="function"&&next()'));
globalShortcut.register('MediaPreviousTrack', () => execSafe('typeof prev==="function"&&prev()'));
}