#!/usr/bin/env node 'use strict' const {exec} = require('child_process'); const fs = require('fs').promises; function execAsync(command, opts) { return new Promise((resolve, reject) => { exec(command, opts, (error, stdout, stderr) => { if (error) { reject(error); } else { resolve({stdout, stderr}); } }); }); } function sleep(n) { return new Promise((resolve) => setTimeout(resolve, n)); } async function findInterface() { const {stdout} = await execAsync('iw dev'); const lines = stdout.split('\n') .map((line) => line.trim()) .filter((line) => line.startsWith('Interface ')) .map((line) => line.split(' ')[1]); return lines[0]; } async function scanInterface(iface) { const {stdout} = await execAsync(`sudo iw dev ${iface} scan`); return stdout; } function formatScanResult(scanResult) { const results = []; let partial = null; for (let line of scanResult.split('\n')) { if (line.startsWith('BSS ')) { finishPartial(); partial = {}; partial.bssid = line.match(/[a-z0-9:]+/)[0]; partial.associated = line.indexOf('associated') >= 0 ? '**' : ''; } line = line.trim() if (line.startsWith('SSID: ')) { partial.ssid = line.split(':')[1].trim(); } if (line.startsWith('signal: ')) { partial.signal = line.split(':')[1].trim(); } if (line.startsWith('DS Parameter set: channel')) { partial.channel = line.split(':')[1].trim(); } if (line.startsWith('* primary channel:')) { partial.channel = 'channel ' + line.split(':')[1].trim(); } if (line.startsWith('freq: ')) { partial.freq = 'freq ' + line.split(':')[1].trim(); } } finishPartial(); function finishPartial() { if (!partial) { return; } partial.ssid = partial.ssid || ''; partial.channel = partial.channel || partial.freq || ''; const sortKey = [ parseFloat(partial.signal), parseInt(partial.channel.split(' ')[1]) ]; results.push([sortKey, partial]); } return results .sort() .map(([, {bssid, ssid, signal, channel, associated}]) => { ssid = ssid.padStart(40, ' ').substr(0, 40); channel = channel.padEnd(12, ' '); return `${signal} ${channel} ${ssid} ${bssid} ${associated}`; }) .join('\n') + '\n'; } async function main() { const iface = process.argv[2] || await findInterface(); if (iface === '-') { const scanResult = await fs.readFile('/dev/stdin', 'utf-8'); const prettyScanResult = formatScanResult(scanResult); process.stdout.write(prettyScanResult); } else { for (;;) { const scanResult = await scanInterface(iface).catch((err) => err.toString()); const prettyScanResult = formatScanResult(scanResult); process.stdout.write('\x1b[2J\x1b[0f'); process.stdout.write(prettyScanResult); await sleep(3000); } } } main().catch((err) => { console.log('Unhandled error!', err); process.exit(1); });