Skip to content

Instantly share code, notes, and snippets.

@Garfonso
Created May 17, 2020 12:22
Show Gist options
  • Save Garfonso/9b5bbc0ade86ad82185edf3208c72017 to your computer and use it in GitHub Desktop.
Save Garfonso/9b5bbc0ade86ad82185edf3208c72017 to your computer and use it in GitHub Desktop.
Manipulate FritzBox 6490 (and maybe other) hosts lists.
//manipulate FritzBox Hosts list using data.lua!
'use strict';
const axios = require('axios').default;
const crypto = require('crypto');
function buildUrl(host, path) {
return 'http://' + host + path;
}
class FirtzBoxLibDataLua {
/**
* Constructor
* @param {Object} options
* @param {string} options.username
* @param {string} options.password
* @param {string} [options.host] defaults to 'fritz.box'
* @param {boolean} [options.debug]
*/
constructor(options) {
this.username = options.username;
this.password = options.password;
this.host = options.host || 'fritz.box';
this.debug = options.debug || false;
this.sessionId = '';
}
log(...parameters) {
console.log.apply(console, parameters);
}
logDebug(...parameters) {
if(this.debug) {
console.log.apply(console, parameters);
}
}
/**
* Login to fritzbox.
* @returns {Promise<boolean>}
*/
async login() {
let result = await axios.get(buildUrl(this.host, '/login_sid.lua'));
//this.logDebug('Login first result: ', result);
const challenge = result.data.match('<Challenge>(.*?)</Challenge>')[1];
this.logDebug('challenge:', challenge);
const hash = crypto.createHash('md5');
hash.update(Buffer.from(challenge + '-' + this.password, 'utf16le'));
const challengeResponse = challenge + '-' + hash.digest('hex');
const path = '/login_sid.lua?username=' + this.username + '&response=' + challengeResponse;
this.logDebug('Calling:', path);
result = await axios.get(buildUrl(this.host, path));
//this.logDebug('Login second result: ', result);
this.sessionId = result.data.match('<SID>(.*?)</SID>')[1];
if (this.sessionId === '0000000000000000') {
this.logDebug('Could not login:', result.data);
throw new Error('could not login: ' + result.data);
} else {
this.logDebug('Login success, sid:', this.sessionId);
return true;
}
}
/**
* Get all network devices from fritzBox
* @returns {Promise<Buffer|any[]|string>}
*/
async getNetworkDevices() {
let data = 'xhr=1&sid=' + this.sessionId + '&lang=de&page=netDev&xhrId=all&no_sidrenew=';
let result = await axios.post(buildUrl(this.host, '/data.lua'), data);
return result.data.data.active.concat(result.data.data.passive);
}
/**
* update Device data
* @param {object} oldData device from FritzBox
* @param {{name: string, ip: string}} newData
* @returns {Promise<boolean|any|{errorText: any, status: number}>}
*/
async updateDevice(oldData, newData) {
/*let data = 'dev_name=' + newData.name || oldData.name +
'&btn_reset_name=&dev_ip=' + newData.ip || oldData.ipv4 + '&static_dhcp=on&kisi_profile=filtprof1' +
'&back_to_page=%2Fnet%2Fnet_overview.lua&dev=' + oldData.UID + '&last_action=&validate=btn_save&xhr=1&useajax=1';*/
//return axios.post(buildUrl(this.host, '/net/edit_device.lua?sid=' + this.sessionId), data);
let data = 'xhr=1&sid=' + this.sessionId + '&lang=de&no_sidrenew=&';
if (newData.name) {
data += 'plc_desc=' + oldData.name + '&dev_name=' + newData.name;
} else {
data += 'dev_name=' + oldData.name;
}
if (newData.ip) {
data += '&dev_ip=' + newData.ip;
} else {
data += '&dev_ip=' + oldData.ipv4;
}
data += '&static_dhcp=on&kisi_profile=';
data += 'filtprof1&back_to_page=%2Fnet%2Fnet_overview.lua';
data += '&dev=' + oldData.UID + '&last_action=&btn_save=&oldpage=%2Fnet%2Fedit_device.lua';
let result = await axios.post(buildUrl(this.host, '/data.lua'), data);
if (typeof result.data === 'string') {
return {status: result.status, errorText: result.data.substring(0, result.data.indexOf('<script>')) || result.data};
} else if (result.data.pid !== 'netDev') {
return result.data;
} else {
return true;
}
}
/**
* Delete device from FritzBox. This sometimes does not work or needs to repeated. Not sure why
* (But it does not always work in FB UI either... hm)
* @param oldData
* @returns {Promise<boolean|any|{errorText: any, status: number}>}
*/
async deleteDevice(oldData) {
let data = 'xhr=1&Verbindung=on&IP-Adresse=on&MAC-Adresse=on&Eigenschaften=on&delete=' + oldData.UID +
'&deleteable=true&devname=' + oldData.name +
'&devtype=unknown&sid=' + this.sessionId + '&lang=de&page=netDev';
await axios.post(buildUrl(this.host, '/data.lua'), data);
data += '&confirmed=';
let result = await axios.post(buildUrl(this.host, '/data.lua'), data);
//this.logDebug('Result:', result.status, 'msg:', result.data);
if (typeof result.data === 'string') {
return {status: result.status, errorText: result.data.substring(0, result.data.indexOf('<script>')) || result.data};
} else if (result.data.pid !== 'netDev') {
return result.data;
} else {
return true;
}
}
/**
*
* @param {{name: string, ip: string, mac: string}} newData
* @returns {Promise<boolean|any|{errorText: any, status: number}>}
*/
async createDevice(newData) {
const macParts = newData.mac.split(':');
const ipParts = newData.ip.split('.');
let data = 'xhr=1&sid=' + this.sessionId;
data += '&lang=de&no_sidrenew=&name=' + newData.name;
for (let i = 0; i < 6; i += 1) {
data += '&mac' + i + '=' + macParts[i];
}
for (let i = 0; i < 4; i += 1) {
data += '&ip' + i + '=' + ipParts[i];
}
data += '&back_to_page=%2Fnet%2Fnet_overview.lua&apply=&oldpage=%2Fnet%2Fnewdevice.lua';
const result = await axios.post(buildUrl(this.host, '/data.lua'), data);
if (typeof result.data === 'string') {
return {status: result.status, errorText: result.data.substring(0, result.data.indexOf('<script>')) || result.data};
} else if (result.data.pid !== 'netDev') {
return result.data;
} else {
return true;
}
}
}
module.exports = FirtzBoxLibDataLua;
@Garfonso
Copy link
Author

Use like this:

const FritzBoxLibDataLua = require('./firtzBoxLibDataLua');

async function main() {
    //login to fritzbox -> gets sessionId.
    const fb = new FritzBoxLibDataLua({ username: 'FritzBox', password: 'YOUR PASSWORD'});
    await fb.login();

    let fbDevices = await fb.getNetworkDevices();
    for (const fbDevice of fbDevices) {
        //do something here, like writing to your own file or comparing with other devices file or so
        //you can update like. Both are optional.
        //const result = await fb.updateDevice(fbDevice, {name: "newName", ip: "192.168.0.244"});
        //you can delete like this:
        //await fb.deleteDevice(fbDevice); //sometimes does not work or needs two tries... not sure why.
    }
    //add new devices:
    await fb.createDevice({mac: "someMac", name: "someName", ip: "someIp"});
}

main().then((r) => {
    console.log('All done:', r);
}, (e) => {
    console.log('Had error:', e);
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment