Kraft102's picture
Update backend source
34367da verified
import { exec } from 'child_process';
import express from 'express';
import geoip from 'geoip-lite';
import si from 'systeminformation';
import axios from 'axios';
const router = express.Router();
// Ordbog: Map procesnavne til Firmaer
const PROVIDER_MAP: Record<string, string> = {
'chrome': 'Google',
'GoogleUpdate': 'Google',
'Adobe Desktop Service': 'Adobe',
'Creative Cloud': 'Adobe',
'Photoshop': 'Adobe',
'Illustrator': 'Adobe',
'After Effects': 'Adobe',
'Premiere Pro': 'Adobe',
'Spotify': 'Spotify AB',
'steam': 'Valve',
'steamwebhelper': 'Valve',
'steam.exe': 'Valve',
'NVIDIA Share': 'NVIDIA',
'nvcontainer': 'NVIDIA',
'GeForce Experience': 'NVIDIA',
'nvsphelper': 'NVIDIA',
'Discord': 'Discord Inc',
'Teams': 'Microsoft',
'OneDrive': 'Microsoft',
'msedge': 'Microsoft',
'EpicGamesLauncher': 'Epic Games',
'Battle.net': 'Blizzard',
'Origin': 'EA',
'Ubisoft Connect': 'Ubisoft',
'Zoom': 'Zoom',
'Slack': 'Slack',
'WhatsApp': 'Meta',
'Facebook': 'Meta',
'Instagram': 'Meta',
'Messenger': 'Meta'
};
interface ConnectionData {
provider: string;
process: string;
destination: string;
port: number;
}
router.get('/spy', async (req, res) => {
try {
// PowerShell kommando til at hente aktive TCP forbindelser
const psCmd = `
Get-NetTCPConnection -State Established |
Where-Object { $_.RemoteAddress -ne "127.0.0.1" -and $_.RemoteAddress -ne "::1" -and $_.RemoteAddress -notlike "192.168.*" -and $_.RemoteAddress -notlike "10.*" -and $_.RemoteAddress -notlike "172.*" } |
Select-Object OwningProcess, RemoteAddress, RemotePort, @{Name="ProcessName";Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
ConvertTo-Json -Depth 2
`;
exec(`powershell -Command "${psCmd}"`, { shell: 'powershell.exe' }, (err, stdout, stderr) => {
if (err) {
console.error('PowerShell error:', err);
console.error('stderr:', stderr);
return res.status(500).json({
error: "Failed to execute PowerShell command",
details: err.message
});
}
try {
// Håndter tom output
if (!stdout || stdout.trim() === '') {
return res.json({ connections: [], stats: {} });
}
const connections = JSON.parse(stdout);
// Håndter array vs enkelt objekt bug i PowerShell JSON
const list = Array.isArray(connections) ? connections : [connections];
// Filtrer ud null/undefined værdier
const validConnections = list.filter((conn: any) => conn && conn.ProcessName);
// Analyser data
const report: ConnectionData[] = validConnections.map((conn: any) => {
// Tjek hvem ejeren er baseret på process navn
const procName = conn.ProcessName || "Unknown";
let provider = "Other";
// Enkel match logic
for (const [key, value] of Object.entries(PROVIDER_MAP)) {
if (procName.toLowerCase().includes(key.toLowerCase())) {
provider = value;
break;
}
}
return {
provider,
process: procName,
destination: conn.RemoteAddress,
port: conn.RemotePort
};
});
// Gruppér til frontend (Stats)
const stats = report.reduce((acc: Record<string, number>, curr: ConnectionData) => {
acc[curr.provider] = (acc[curr.provider] || 0) + 1;
return acc;
}, {});
res.json({
connections: report,
stats,
timestamp: new Date().toISOString()
});
} catch (parseError) {
console.error('JSON parse error:', parseError);
console.error('Raw output:', stdout);
res.status(500).json({
error: "Data parse error",
details: parseError instanceof Error ? parseError.message : "Unknown parse error"
});
}
});
} catch (error) {
console.error('Network spy error:', error);
res.status(500).json({
error: "Internal server error",
details: error instanceof Error ? error.message : "Unknown error"
});
}
});
// EU country codes for GDPR compliance
const EU_COUNTRIES = new Set([
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'
]);
// Get geo information for IP
router.get('/geo/:ip', async (req, res) => {
try {
const { ip } = req.params;
const geo = geoip.lookup(ip);
if (!geo) {
return res.json({ error: 'IP not found', ip });
}
const isEU = EU_COUNTRIES.has(geo.country);
const countryCode = geo.country;
const country = geo.country; // Could be enhanced with country name lookup
res.json({
ip,
countryCode,
country,
isEU,
city: geo.city,
region: geo.region,
timezone: geo.timezone,
ll: geo.ll // latitude, longitude
});
} catch (error) {
res.status(500).json({ error: 'Geo lookup failed' });
}
});
// Get electricity cost estimation
router.get('/power-cost', async (req, res) => {
try {
// Get current system load
const cpuLoad = await si.currentLoad();
const memInfo = await si.mem();
// Estimate power consumption (simplified)
// CPU: TDP * load percentage + base consumption
const cpuWatts = (65 * (cpuLoad.currentLoad / 100)) + 50; // 65W TDP CPU, 50W base
// Memory: ~2W per GB used
const memWatts = (memInfo.used / (1024 ** 3)) * 2;
// GPU: Estimate based on activity (simplified)
const gpuWatts = 150; // Base GPU consumption
// Screen and other components
const baseWatts = 100;
const totalWatts = cpuWatts + memWatts + gpuWatts + baseWatts;
// Get current electricity price (simplified - in real app use Energinet API)
// Current Danish electricity price approximation (øre/kWh)
const pricePerKwh = 250; // 2.50 kr/kWh = 250 øre/kWh
const costPerHour = (totalWatts / 1000) * (pricePerKwh / 100); // kr/time
res.json({
watts: Math.round(totalWatts),
costPerHour: costPerHour.toFixed(2),
breakdown: {
cpu: Math.round(cpuWatts),
memory: Math.round(memWatts),
gpu: Math.round(gpuWatts),
base: baseWatts
},
pricePerKwh: pricePerKwh / 100, // kr/kWh
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Power cost calculation error:', error);
res.status(500).json({ error: 'Power calculation failed' });
}
});
// Get busy ports information
router.get('/ports', async (req, res) => {
try {
const psCmd = `
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -ne 0 } |
Select-Object LocalPort, OwningProcess, @{Name="ProcessName";Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
ConvertTo-Json -Depth 2
`;
exec(`powershell -Command "& { ${psCmd} }"`, { shell: 'powershell.exe' }, (err, stdout, stderr) => {
if (err) {
console.error('Port scan error:', err);
return res.status(500).json({ error: 'Port scan failed' });
}
try {
const ports = JSON.parse(stdout || '[]');
const list = Array.isArray(ports) ? ports : [ports];
const busyPorts = list
.filter((p: any) => p && p.ProcessName)
.map((p: any) => ({
port: p.LocalPort,
pid: p.OwningProcess,
process: p.ProcessName
}));
res.json({ busyPorts });
} catch (parseError) {
res.status(500).json({ error: 'Port data parse failed' });
}
});
} catch (error) {
res.status(500).json({ error: 'Port scan error' });
}
});
// Kill process by PID
router.post('/kill-process/:pid', async (req, res) => {
try {
const { pid } = req.params;
exec(`taskkill /PID ${pid} /F`, (err, stdout, stderr) => {
if (err) {
return res.status(500).json({ error: 'Failed to kill process', details: stderr });
}
res.json({ success: true, pid: parseInt(pid) });
});
} catch (error) {
res.status(500).json({ error: 'Kill process error' });
}
});
// Get system processes for homework mode
router.get('/processes', async (req, res) => {
try {
const psCmd = `
Get-Process |
Where-Object { $_.ProcessName -match '^(steam|discord|epicgameslauncher|chrome|firefox|edge)$' } |
Select-Object Id, ProcessName, @{Name="MemoryMB";Expression={[math]::Round($_.WorkingSet64 / 1MB, 2)}} |
ConvertTo-Json -Depth 2
`;
exec(`powershell -Command "& { ${psCmd} }"`, { shell: 'powershell.exe' }, (err, stdout, stderr) => {
if (err) {
return res.json({ processes: [] });
}
try {
const processes = JSON.parse(stdout || '[]');
const list = Array.isArray(processes) ? processes : [processes];
res.json({ processes: list });
} catch (parseError) {
res.json({ processes: [] });
}
});
} catch (error) {
res.json({ processes: [] });
}
});
// Homework mode: Kill gaming apps and block distracting sites
router.post('/homework-mode', async (req, res) => {
try {
const killCmd = `
$processes = Get-Process | Where-Object { $_.ProcessName -match '^(steam|discord|epicgameslauncher)$' }
foreach ($proc in $processes) {
Stop-Process -Id $proc.Id -Force
}
`;
exec(`powershell -Command "& { ${killCmd} }"`, (err, stdout, stderr) => {
// Even if killing fails, we continue with hosts file modification
const hostsBlock = `
# WidgeTDC Homework Mode - Block distracting sites
127.0.0.1 youtube.com
127.0.0.1 www.youtube.com
127.0.0.1 tiktok.com
127.0.0.1 www.tiktok.com
127.0.0.1 instagram.com
127.0.0.1 www.instagram.com
127.0.0.1 facebook.com
127.0.0.1 www.facebook.com
`;
// Note: Modifying hosts file requires admin privileges
// In production, this would need to be handled differently or with elevated permissions
res.json({
success: true,
message: 'Homework mode activated',
blockedApps: ['steam', 'discord', 'epicgameslauncher'],
blockedSites: ['youtube.com', 'tiktok.com', 'instagram.com', 'facebook.com']
});
});
} catch (error) {
res.status(500).json({ error: 'Homework mode activation failed' });
}
});
// Get Docker containers status
router.get('/docker/containers', async (req, res) => {
try {
const psCmd = `
docker ps --format "json" 2>$null | ConvertTo-Json -Depth 3
`;
exec(`powershell -Command "& { ${psCmd} }"`, { shell: 'powershell.exe' }, (err, stdout, stderr) => {
if (err) {
// Docker might not be running or installed
return res.json({ containers: [], error: 'Docker not available' });
}
try {
const containers = JSON.parse(stdout || '[]');
const list = Array.isArray(containers) ? containers : [containers];
// Get stats for each container
const containersWithStats = list.map(async (container: any) => {
try {
const statsCmd = `docker stats --no-stream --format "json" ${container.ID}`;
const statsOutput = await new Promise<string>((resolve, reject) => {
exec(`powershell -Command "& { ${statsCmd} }"`, { shell: 'powershell.exe' }, (err, stdout) => {
if (err) reject(err);
else resolve(stdout);
});
});
const stats = JSON.parse(statsOutput);
return {
id: container.ID,
name: container.Names,
image: container.Image,
status: container.Status,
ports: container.Ports,
cpu: stats.CPUPerc,
memory: stats.MemUsage,
memoryPerc: stats.MemPerc,
netIO: stats.NetIO,
blockIO: stats.BlockIO
};
} catch (statsError) {
// Return container info without stats
return {
id: container.ID,
name: container.Names,
image: container.Image,
status: container.Status,
ports: container.Ports,
error: 'Stats unavailable'
};
}
});
Promise.all(containersWithStats).then(containers => {
res.json({ containers });
});
} catch (parseError) {
res.json({ containers: [], error: 'Parse error' });
}
});
} catch (error) {
res.status(500).json({ error: 'Docker monitoring error' });
}
});
// Stop Docker container
router.post('/docker/containers/:id/stop', async (req, res) => {
try {
const { id } = req.params;
exec(`docker stop ${id}`, (err, stdout, stderr) => {
if (err) {
return res.status(500).json({ error: 'Failed to stop container', details: stderr });
}
res.json({ success: true, containerId: id, action: 'stopped' });
});
} catch (error) {
res.status(500).json({ error: 'Stop container error' });
}
});
// Start Docker container
router.post('/docker/containers/:id/start', async (req, res) => {
try {
const { id } = req.params;
exec(`docker start ${id}`, (err, stdout, stderr) => {
if (err) {
return res.status(500).json({ error: 'Failed to start container', details: stderr });
}
res.json({ success: true, containerId: id, action: 'started' });
});
} catch (error) {
res.status(500).json({ error: 'Start container error' });
}
});
// Restart Docker container
router.post('/docker/containers/:id/restart', async (req, res) => {
try {
const { id } = req.params;
exec(`docker restart ${id}`, (err, stdout, stderr) => {
if (err) {
return res.status(500).json({ error: 'Failed to restart container', details: stderr });
}
res.json({ success: true, containerId: id, action: 'restarted' });
});
} catch (error) {
res.status(500).json({ error: 'Restart container error' });
}
});
// Get comprehensive system monitoring data (God Mode)
router.get('/system', async (req, res) => {
try {
// Get CPU information
const cpuData = await si.cpu();
const cpuLoad = await si.currentLoad();
// Get memory information
const memData = await si.mem();
// Get GPU information
const graphics = await si.graphics();
// Get processes (top 5 by CPU usage)
const processData = await si.processes();
const topProcesses = processData.list
.sort((a, b) => b.cpu - a.cpu)
.slice(0, 5)
.map(p => ({
name: p.name || 'Unknown',
pid: p.pid,
cpu: Math.round(p.cpu * 10) / 10, // Round to 1 decimal
mem: Math.round(p.mem * 10) / 10, // Memory percentage
memBytes: p.memVsz // Memory in bytes
}));
// Get temperatures (if available)
let tempData = null;
try {
tempData = await si.cpuTemperature();
} catch (e) {
// Temperatures not available on all systems
}
// Get network stats
const networkStats = await si.networkStats();
// Calculate total network usage
const totalRx = networkStats.reduce((sum, iface) => sum + iface.rx_bytes, 0);
const totalTx = networkStats.reduce((sum, iface) => sum + iface.tx_bytes, 0);
// Get disk usage
const diskData = await si.fsSize();
res.json({
cpu: {
manufacturer: cpuData.manufacturer,
brand: cpuData.brand,
cores: cpuData.cores,
load: Math.round(cpuLoad.currentLoad * 10) / 10,
loadPerCore: cpuLoad.cpus.map(core => Math.round(core.load * 10) / 10)
},
memory: {
total: memData.total,
used: memData.used,
free: memData.free,
usedPercent: Math.round((memData.used / memData.total) * 100 * 10) / 10
},
gpu: graphics.controllers.map(gpu => ({
model: gpu.model,
vendor: gpu.vendor,
temperature: (gpu as any).temperatureGpu || (gpu as any).temperature,
memoryTotal: gpu.memoryTotal,
memoryUsed: gpu.memoryUsed,
utilizationGpu: gpu.utilizationGpu,
utilizationMemory: gpu.utilizationMemory
})),
processes: topProcesses,
temperature: tempData ? {
main: Math.round(tempData.main * 10) / 10,
cores: tempData.cores?.map(t => Math.round(t * 10) / 10) || []
} : null,
network: {
totalRx,
totalTx,
interfaces: networkStats.map(iface => ({
iface: iface.iface,
rx_bytes: iface.rx_bytes,
tx_bytes: iface.tx_bytes,
rx_sec: iface.rx_sec,
tx_sec: iface.tx_sec
}))
},
disks: diskData.map(disk => ({
fs: disk.fs,
type: disk.type,
size: disk.size,
used: disk.used,
available: disk.available,
use: disk.use,
mount: disk.mount
})),
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('System monitoring error:', error);
res.status(500).json({
error: 'System monitoring failed',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// MCP AI Chat - Natural language system control
router.post('/mcp-chat', async (req, res) => {
try {
const { message } = req.body;
if (!message || typeof message !== 'string') {
return res.status(400).json({ error: 'Message is required' });
}
const lowerMessage = message.toLowerCase();
// Rule-based command recognition (can be extended with AI)
let command = null;
let description = '';
// Process management commands
if (lowerMessage.includes('dræb') || lowerMessage.includes('kill') || lowerMessage.includes('stop')) {
if (lowerMessage.includes('node') || lowerMessage.includes('nodejs')) {
command = 'taskkill /F /IM node.exe';
description = 'Dræber alle Node.js processer';
} else if (lowerMessage.includes('chrome') || lowerMessage.includes('browser')) {
command = 'taskkill /F /IM chrome.exe';
description = 'Dræber alle Chrome processer';
} else if (lowerMessage.includes('steam')) {
command = 'taskkill /F /IM steam.exe';
description = 'Dræber Steam';
} else if (lowerMessage.includes('discord')) {
command = 'taskkill /F /IM discord.exe';
description = 'Dræber Discord';
}
}
// File system commands
else if (lowerMessage.includes('ryd') || lowerMessage.includes('clean') || lowerMessage.includes('slet')) {
if (lowerMessage.includes('download') || lowerMessage.includes('downloads')) {
command = 'del /Q "%USERPROFILE%\\Downloads\\*.*" 2>nul';
description = 'Rydder Downloads mappen';
} else if (lowerMessage.includes('temp') || lowerMessage.includes('midlertidig')) {
command = 'del /Q /F /S "%TEMP%\\*.*" 2>nul && del /Q /F /S "%TMP%\\*.*" 2>nul';
description = 'Rydder midlertidige filer';
}
}
// System commands
else if (lowerMessage.includes('genstart') || lowerMessage.includes('restart')) {
if (lowerMessage.includes('computer') || lowerMessage.includes('pc')) {
command = 'shutdown /r /t 0';
description = 'Genstarter computeren';
} else if (lowerMessage.includes('wifi') || lowerMessage.includes('netværk')) {
command = 'netsh interface set interface "Wi-Fi" disable && timeout 5 && netsh interface set interface "Wi-Fi" enable';
description = 'Genstarter Wi-Fi adapter';
}
}
// Status commands
else if (lowerMessage.includes('status') || lowerMessage.includes('hvordan går det') || lowerMessage.includes('system')) {
// Get system status
const cpuLoad = await si.currentLoad();
const memData = await si.mem();
description = `System status: CPU ${Math.round(cpuLoad.currentLoad)}%, RAM ${Math.round((memData.used / memData.total) * 100)}% brugt`;
command = 'echo System status OK';
}
// Docker commands
else if (lowerMessage.includes('docker') || lowerMessage.includes('container')) {
if (lowerMessage.includes('stop') || lowerMessage.includes('dræb')) {
command = 'docker stop $(docker ps -q)';
description = 'Stopper alle kørende Docker containere';
} else if (lowerMessage.includes('start') || lowerMessage.includes('kør')) {
command = 'docker start $(docker ps -a -q)';
description = 'Starter alle Docker containere';
}
}
// Homework mode
else if (lowerMessage.includes('lektie') || lowerMessage.includes('homework') || lowerMessage.includes('fokus')) {
// This will trigger the homework mode we already have
description = 'Aktiverer lektie-mode: lukker gaming apps og blokerer distraherende sites';
command = 'homework-mode'; // Special marker for our existing endpoint
}
// If no command recognized, provide helpful response
if (!command) {
const suggestions = [
'Prøv: "Dræb alle Node processer"',
'Prøv: "Ryd Downloads mappen"',
'Prøv: "Aktiver lektie-mode"',
'Prøv: "Genstart Wi-Fi"',
'Prøv: "System status"'
];
return res.json({
response: `Jeg forstod ikke "${message}". Her er nogle ting jeg kan gøre:`,
suggestions,
success: false
});
}
// Execute the command
if (command === 'homework-mode') {
// Special case - call our existing homework mode
try {
// This would normally call the homework mode function
// For now, just return success
return res.json({
response: description,
executed: true,
command: 'Homework mode activated'
});
} catch (error) {
return res.json({
response: 'Kunne ikke aktivere lektie-mode',
executed: false,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
// Execute shell command
exec(command, { shell: 'powershell.exe' }, (error, stdout, stderr) => {
if (error) {
console.error('Command execution error:', error);
return res.json({
response: `Fejl ved udførelse af kommando: ${error.message}`,
executed: false,
error: stderr || error.message
});
}
res.json({
response: `${description} - Udført!`,
executed: true,
output: stdout || 'Kommando udført succesfuldt',
command: command
});
});
} catch (error) {
console.error('MCP Chat error:', error);
res.status(500).json({
error: 'MCP Chat failed',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Get Git repository status
router.get('/git-status/:path', async (req, res) => {
try {
const { path } = req.params;
const decodedPath = decodeURIComponent(path);
// Check if it's a git repository
exec(`cd "${decodedPath}" && git status --porcelain`, (error, stdout, stderr) => {
if (error) {
return res.json({ isGit: false, error: 'Not a git repository' });
}
const changes = stdout.trim().split('\n').filter(line => line.trim());
const hasChanges = changes.length > 0;
// Get branch info
exec(`cd "${decodedPath}" && git branch --show-current`, (branchError, branchStdout) => {
const currentBranch = branchError ? 'unknown' : branchStdout.trim();
// Check if ahead/behind origin
exec(`cd "${decodedPath}" && git status -sb`, (statusError, statusStdout) => {
let ahead = 0;
let behind = 0;
if (!statusError) {
const match = statusStdout.match(/ahead (\d+)/);
const behindMatch = statusStdout.match(/behind (\d+)/);
ahead = match ? parseInt(match[1]) : 0;
behind = behindMatch ? parseInt(behindMatch[1]) : 0;
}
// Get recent commits
exec(`cd "${decodedPath}" && git log --oneline -5`, (logError, logStdout) => {
const recentCommits = logError ? [] : logStdout.trim().split('\n').filter(line => line.trim());
res.json({
isGit: true,
path: decodedPath,
hasChanges,
changesCount: changes.length,
currentBranch,
ahead,
behind,
recentCommits,
changes: hasChanges ? changes.slice(0, 10) : []
});
});
});
});
});
} catch (error) {
res.status(500).json({ error: 'Git status check failed' });
}
});
// Get current media playback info
router.get('/media', async (req, res) => {
try {
// This would need Windows Media Transport Controls integration
// For now, return a placeholder
res.json({
isPlaying: false,
title: null,
artist: null,
album: null,
position: 0,
duration: 0,
supported: false,
message: 'Media control requires Windows Media Transport Controls integration'
});
} catch (error) {
res.status(500).json({ error: 'Media info retrieval failed' });
}
});
// Control media playback
router.post('/media/:action', async (req, res) => {
try {
const { action } = req.params;
// Placeholder for media control
// Would integrate with Windows Media Keys API
res.json({
action,
success: false,
message: 'Media control not yet implemented - requires Windows API integration'
});
} catch (error) {
res.status(500).json({ error: 'Media control failed' });
}
});
export default router;