@@ -108,9 +108,10 @@ interface AppProps {
108108 useTls ?: boolean ;
109109 insecure ?: boolean ;
110110 pcapFile ?: string ;
111+ bot ?: boolean ;
111112}
112113
113- export function App ( { address, packetStore, nodeStore, skipConfig = false , skipNodes = false , meshViewUrl, useFahrenheit = false , httpPort, useTls = false , insecure = false , pcapFile } : AppProps ) {
114+ export function App ( { address, packetStore, nodeStore, skipConfig = false , skipNodes = false , meshViewUrl, useFahrenheit = false , httpPort, useTls = false , insecure = false , pcapFile, bot = false } : AppProps ) {
114115 const { exit } = useApp ( ) ;
115116 const { stdout } = useStdout ( ) ;
116117 const [ transport , setTransport ] = useState < Transport | null > ( null ) ;
@@ -145,6 +146,8 @@ export function App({ address, packetStore, nodeStore, skipConfig = false, skipN
145146 const nodeUpdateQueueRef = useRef < NodeData [ ] > ( [ ] ) ;
146147 const nodeUpdateTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
147148 const pcapWriterRef = useRef < PcapWriter | null > ( null ) ;
149+ const transportRef = useRef < Transport | null > ( null ) ;
150+ transportRef . current = transport ;
148151
149152 // Reboot modal state
150153 const [ showRebootModal , setShowRebootModal ] = useState ( false ) ;
@@ -583,6 +586,57 @@ export function App({ address, packetStore, nodeStore, skipConfig = false, skipN
583586 setDmMessages ( db . getDMMessages ( myNodeNum , selectedConvo . nodeNum ) ) ;
584587 }
585588 }
589+
590+ // Bot auto-reply to "ping" and "test"
591+ if ( bot && mp . from !== myNodeNum && transportRef . current ) {
592+ const trimmed = ( packet . payload as string ) . trim ( ) . toLowerCase ( ) ;
593+ if ( trimmed === "ping" || trimmed === "test" ) {
594+ const parts = [ trimmed === "ping" ? "pong" : "ack" ] ;
595+ if ( mp . rxSnr != null ) parts . push ( `SNR:${ mp . rxSnr } dB` ) ;
596+ if ( mp . rxRssi != null && mp . rxRssi !== 0 ) parts . push ( `RSSI:${ mp . rxRssi } dBm` ) ;
597+ if ( mp . hopStart ) parts . push ( `hops:${ mp . hopStart - ( mp . hopLimit ?? 0 ) } /${ mp . hopStart } ` ) ;
598+ const reply = parts . join ( " | " ) ;
599+ const isDM = mp . to !== BROADCAST_ADDR ;
600+
601+ const packetId = Math . floor ( Math . random ( ) * 0xffffffff ) ;
602+ const data = create ( Mesh . DataSchema , {
603+ portnum : Portnums . PortNum . TEXT_MESSAGE_APP ,
604+ payload : new TextEncoder ( ) . encode ( reply ) ,
605+ } ) ;
606+ const meshPacket = create ( Mesh . MeshPacketSchema , {
607+ id : packetId ,
608+ from : myNodeNum ,
609+ to : isDM ? mp . from : BROADCAST_ADDR ,
610+ channel : isDM ? 0 : mp . channel ,
611+ wantAck : true ,
612+ payloadVariant : { case : "decoded" , value : data } ,
613+ } ) ;
614+ const toRadio = create ( Mesh . ToRadioSchema , {
615+ payloadVariant : { case : "packet" , value : meshPacket } ,
616+ } ) ;
617+
618+ const t = transportRef . current ;
619+ const binary = toBinary ( Mesh . ToRadioSchema , toRadio ) ;
620+ t . send ( binary ) . then ( ( ) => {
621+ const botMsg : db . DbMessage = {
622+ packetId,
623+ fromNode : myNodeNum ,
624+ toNode : isDM ? mp . from : BROADCAST_ADDR ,
625+ channel : isDM ? 0 : mp . channel ,
626+ text : reply ,
627+ timestamp : Math . floor ( Date . now ( ) / 1000 ) ,
628+ status : "pending" ,
629+ } ;
630+ db . insertMessage ( botMsg ) ;
631+ if ( ! isDM ) {
632+ setMessages ( prev => [ ...prev , botMsg ] . slice ( - 100 ) ) ;
633+ }
634+ Logger . info ( "Bot" , `Auto-replied "${ reply } " to ${ formatNodeId ( mp . from ) } ` , { isDM } ) ;
635+ } ) . catch ( ( ) => {
636+ Logger . warn ( "Bot" , `Failed to auto-reply to ${ formatNodeId ( mp . from ) } ` ) ;
637+ } ) ;
638+ }
639+ }
586640 }
587641
588642 // Handle routing ACK/NAK for our sent messages
0 commit comments