3333from meshcore import MeshCore , EventType , logger
3434
3535# Version
36- VERSION = "v1.2.5 "
36+ VERSION = "v1.2.6 "
3737
3838# default ble address is stored in a config file
3939MCCLI_CONFIG_DIR = str (Path .home ()) + "/.config/meshcore/"
@@ -210,7 +210,7 @@ async def handle_log_rx(event):
210210 pkt = bytes ().fromhex (event .payload ["payload" ])
211211
212212 if handle_log_rx .channel_echoes :
213- if pkt [0 ] == 0x15 :
213+ if pkt [0 ] == 0x15 :
214214 chan_name = ""
215215 path_len = pkt [1 ]
216216 path = pkt [2 :path_len + 2 ].hex ()
@@ -235,7 +235,7 @@ async def handle_log_rx(event):
235235 aes_key = bytes .fromhex (channel ["channel_secret" ])
236236 cipher = AES .new (aes_key , AES .MODE_ECB )
237237 message = cipher .decrypt (msg )[5 :].decode ("utf-8" ).strip ("\x00 " )
238-
238+
239239 if chan_name != "" :
240240 width = os .get_terminal_size ().columns
241241 cars = width - 13 - 2 * path_len - len (chan_name ) - 1
@@ -245,7 +245,7 @@ async def handle_log_rx(event):
245245 print_above (txt )
246246 else :
247247 print (txt )
248-
248+
249249handle_log_rx .json_log_rx = False
250250handle_log_rx .channel_echoes = False
251251handle_log_rx .mc = None
@@ -366,7 +366,7 @@ async def subscribe_to_msgs(mc, json_output=False, above=False):
366366class MyNestedCompleter (NestedCompleter ):
367367 def get_completions ( self , document , complete_event ):
368368 txt = document .text_before_cursor .lstrip ()
369- if not " " in txt :
369+ if not " " in txt :
370370 if txt != "" and txt [0 ] == "/" and txt .count ("/" ) == 1 :
371371 opts = []
372372 for k in self .options .keys ():
@@ -465,6 +465,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
465465 "set_channel" : None ,
466466 "get_channels" : None ,
467467 "remove_channel" : None ,
468+ "apply_to" : None ,
469+ "at" : None ,
468470 "set" : {
469471 "name" : None ,
470472 "pin" : None ,
@@ -531,6 +533,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
531533
532534 contact_completion_list = {
533535 "contact_info" : None ,
536+ "contact_name" : None ,
537+ "contact_lastmod" : None ,
534538 "export_contact" : None ,
535539 "share_contact" : None ,
536540 "upload_contact" : None ,
@@ -652,7 +656,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
652656 slash_root_completion_list = {}
653657 for k ,v in root_completion_list .items ():
654658 slash_root_completion_list ["/" + k ]= v
655-
659+
656660 completion_list .update (slash_root_completion_list )
657661
658662 slash_contacts_completion_list = {}
@@ -905,6 +909,13 @@ def _(event):
905909 if last_ack == False :
906910 contact = ln
907911
912+ elif contact is None and \
913+ (line .startswith ("apply_to " ) or line .startswith ("at " )):
914+ try :
915+ await apply_command_to_contacts (mc , line .split (" " ,2 )[1 ], line .split (" " ,2 )[2 ])
916+ except IndexError :
917+ logger .error (f"Error with apply_to command parameters" )
918+
908919 # commands are passed through if at root
909920 elif contact is None or line .startswith ("." ) :
910921 try :
@@ -966,6 +977,23 @@ async def process_contact_chat_line(mc, contact, line):
966977 await process_cmds (mc , args )
967978 return True
968979
980+ if line .startswith ("contact_name" ) or line .startswith ("cn" ):
981+ print (contact ['adv_name' ],end = "" )
982+ if " " in line :
983+ print (" " , end = "" )
984+ secline = line .split (" " , 1 )[1 ]
985+ await process_contact_chat_line (mc , contact , secline )
986+ else :
987+ print ("" )
988+ return True
989+
990+ if line == "contact_lastmod" :
991+ timestamp = contact ["lastmod" ]
992+ print (f"{ contact ['adv_name' ]} updated"
993+ f" { datetime .datetime .fromtimestamp (timestamp ).strftime ('%Y-%m-%d at %H:%M:%S' )} "
994+ f" ({ timestamp } )" )
995+ return True
996+
969997 # commands that take contact as second arg will be sent to recipient
970998 if line == "sc" or line == "share_contact" or \
971999 line == "ec" or line == "export_contact" or \
@@ -1053,7 +1081,7 @@ async def process_contact_chat_line(mc, contact, line):
10531081 return True
10541082
10551083 # same but for commands with a parameter
1056- if line .startswith ("cmd " ) or \
1084+ if line .startswith ("cmd " ) or line . startswith ( "msg " ) or \
10571085 line .startswith ("cp " ) or line .startswith ("change_path " ) or \
10581086 line .startswith ("cf " ) or line .startswith ("change_flags " ) or \
10591087 line .startswith ("req_binary " ) or \
@@ -1085,7 +1113,7 @@ async def process_contact_chat_line(mc, contact, line):
10851113
10861114 if password == "" :
10871115 try :
1088- sess = PromptSession ("Password: " , is_password = True )
1116+ sess = PromptSession (f "Password for { contact [ 'adv_name' ] } : " , is_password = True )
10891117 password = await sess .prompt_async ()
10901118 except EOFError :
10911119 logger .info ("Canceled" )
@@ -1124,6 +1152,87 @@ async def process_contact_chat_line(mc, contact, line):
11241152
11251153 return False
11261154
1155+ async def apply_command_to_contacts (mc , contact_filter , line ):
1156+ upd_before = None
1157+ upd_after = None
1158+ contact_type = None
1159+ min_hops = None
1160+ max_hops = None
1161+
1162+ filters = contact_filter .split ("," )
1163+ for f in filters :
1164+ if f == "all" :
1165+ pass
1166+ elif f [0 ] == "u" : #updated
1167+ val_str = f [2 :]
1168+ t = time .time ()
1169+ if val_str [- 1 ] == "d" : # value in days
1170+ t = t - float (val_str [0 :- 1 ]) * 86400
1171+ elif val_str [- 1 ] == "h" : # value in hours
1172+ t = t - float (val_str [0 :- 1 ]) * 3600
1173+ elif val_str [- 1 ] == "m" : # value in minutes
1174+ t = t - float (val_str [0 :- 1 ]) * 60
1175+ else :
1176+ t = int (val_str )
1177+ if f [1 ] == "<" : #before
1178+ upd_before = t
1179+ elif f [1 ] == ">" :
1180+ upd_after = t
1181+ else :
1182+ logger .error (f"Time filter can only be < or >" )
1183+ return
1184+ elif f [0 ] == "t" : # type
1185+ if f [1 ] == "=" :
1186+ contact_type = int (f [2 :])
1187+ else :
1188+ logger .error (f"Type can only be equals to a value" )
1189+ return
1190+ elif f [0 ] == "d" : # direct
1191+ min_hops = 0
1192+ elif f [0 ] == "f" : # flood
1193+ max_hops = - 1
1194+ elif f [0 ] == "h" : # hop number
1195+ if f [1 ] == ">" :
1196+ min_hops = int (f [2 :])+ 1
1197+ elif f [1 ] == "<" :
1198+ max_hops = int (f [2 :])- 1
1199+ elif f [1 ] == "=" :
1200+ min_hops = int (f [2 :])
1201+ max_hops = int (f [2 :])
1202+ else :
1203+ logger .error (f"Unknown filter { f } " )
1204+ return
1205+
1206+ for c in dict (mc ._contacts ).items ():
1207+ contact = c [1 ]
1208+ if (contact_type is None or contact ["type" ] == contact_type ) and \
1209+ (upd_before is None or contact ["lastmod" ] < upd_before ) and \
1210+ (upd_after is None or contact ["lastmod" ] > upd_after ) and \
1211+ (min_hops is None or contact ["out_path_len" ] >= min_hops ) and \
1212+ (max_hops is None or contact ["out_path_len" ] <= max_hops ):
1213+ if await process_contact_chat_line (mc , contact , line ):
1214+ pass
1215+
1216+ elif line == "remove_contact" :
1217+ args = [line , contact ['adv_name' ]]
1218+ await process_cmds (mc , args )
1219+
1220+ elif line .startswith ("send" ) or line .startswith ("\" " ) :
1221+ if line .startswith ("send" ) :
1222+ line = line [5 :]
1223+ if line .startswith ("\" " ) :
1224+ line = line [1 :]
1225+ await msg_ack (mc , contact , line )
1226+
1227+ elif contact ["type" ] == 2 or \
1228+ contact ["type" ] == 3 or \
1229+ contact ["type" ] == 4 : # repeater, room, sensor send cmd
1230+ await process_cmds (mc , ["cmd" , contact ["adv_name" ], line ])
1231+ # wait for a reply from cmd
1232+ await mc .wait_for_event (EventType .MESSAGES_WAITING )
1233+
1234+ else :
1235+ logger .error (f"Can't send { line } to { contact ['adv_name' ]} " )
11271236
11281237async def send_cmd (mc , contact , cmd ) :
11291238 res = await mc .commands .send_cmd (contact , cmd )
@@ -2718,7 +2827,7 @@ async def next_cmd(mc, cmds, json_output=False):
27182827 await mc .ensure_contacts ()
27192828 contact = mc .get_contact_by_name (cmds [0 ])
27202829 if contact is None :
2721- logger .error (f"Unknown command : { cmd } . { cmds } not executed ..." )
2830+ logger .error (f"Unknown command : { cmd } , { cmds } not executed ..." )
27222831 return None
27232832
27242833 await interactive_loop (mc , to = contact )
0 commit comments