From 69ccc65babae47897f4e6616b8f47e3e5d3ebc37 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sat, 18 Oct 2025 11:04:39 +0200 Subject: [PATCH] init --- README.md | 20 ++++++++++ index.mjs | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +++ 3 files changed, 131 insertions(+) create mode 100644 README.md create mode 100644 index.mjs create mode 100644 package.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dce9bf --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# MeshCore map auto uploader + +## Description +This bot will upload every repeater or room server to the map when companion hears new advert + +## Requirements +You will need Meshcore device with Companion USB firmware connected to the computer with internet connection + +## Installation +1. [Install Node.js 22 or higher](https://nodejs.org/en/download/)(most recent LTS recommended) +2. Clone this repo & install dependencies via npm +```sh +git clone https://github.com/recrof/map.meshcore.dev-uploader +cd map.meshcore.dev-uploader +npm install . +``` + +## Usage +1. Connect working MeshCore companion usb into the computer +2. run `node index.mjs [usb_port]` diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..17a9724 --- /dev/null +++ b/index.mjs @@ -0,0 +1,106 @@ +import { + BufferUtils, Packet, Constants, + NodeJSSerialConnection, TCPConnection, + Advert +} from '@liamcottle/meshcore.js'; + +import crypto from 'crypto'; + +const device = process.argv[2] ?? '/dev/ttyACM0'; +const apiURL = 'https://map.meshcore.dev/api/v1/uploader/node'; +const seenAdverts = {}; +let clientInfo = {}; + +const signData = async (connection, data) => { + const json = JSON.stringify(data); + const dataHash = new Uint8Array( + await crypto.subtle.digest('SHA-256', new TextEncoder().encode(json)) + ); + + return { data: json, signature: BufferUtils.bytesToHex(await connection.sign(dataHash)) } +} + +const processPacket = async (connection, rawPacket) => { + const packet = Packet.fromBytes(rawPacket); + + if(packet.payload_type_string !== 'ADVERT') return; + + const advert = Advert.fromBytes(packet.payload); + // console.log(advert); + if(advert.parsed.type === 'CHAT') return; + + const pubKey = BufferUtils.bytesToHex(advert.publicKey); + const node = { pubKey, name: advert.parsed.name, ts: advert.timestamp, type: advert.parsed.type.toLowerCase() }; + + if(!advert.isVerified()) { + console.warn('ignoring: signature verification failed', node); + return; + } + + if(seenAdverts[pubKey]) { + if(seenAdverts[pubKey] >= advert.timestamp) { + console.warn('ignoring: possible replay attack', node); + return; + } + if(advert.timestamp < seenAdverts[pubKey] + 3600) { + console.warn('ignoring: timestamp too new to reupload', node) + return; + } + } + + console.log(`uploading`, node); + const data = { + params: { + freq: clientInfo.radioFreq / 1000, + cr: clientInfo.radioCr, + sf: clientInfo.radioSf, + bw: clientInfo.radioBw / 1000 + }, + links: [`meshcore://${BufferUtils.bytesToHex(rawPacket)}`] + }; + + const requestData = await signData(connection, data); + requestData.publicKey = BufferUtils.bytesToHex(clientInfo.publicKey); + + const req = await fetch(apiURL, { + method: 'POST', + body: JSON.stringify(requestData) + }); + + console.log('sending', requestData) + console.log(await req.json()); + + seenAdverts[pubKey] = advert.timestamp; +} + +console.log(`Connecting to ${device}...`); + +let connection; +if(device.startsWith('/') || device.startsWith('COM')){ + connection = new NodeJSSerialConnection(device); +} else { + const [ host, port ] = device.split(':'); + connection = new TCPConnection(host, port ?? 5000); +} + +connection.on('connected', async () => { + console.log(`Connected.`); + + connection.setManualAddContacts(); + + clientInfo = await connection.getSelfInfo(); + // console.log('info', clientInfo); + + console.log('Map uploader waiting for adverts...'); +}); + +connection.on(Constants.PushCodes.LogRxData, async (event) => { + try { + await processPacket(connection, event.raw); + } + catch(e) { + console.error(e); + } +}); + +await connection.connect(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..aac71c6 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@liamcottle/meshcore.js": "^1.10.0" + } +}