micrOSDevToolKit 2.8.7__py3-none-any.whl → 2.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +22 -22
- micrOS/source/Common.py +10 -15
- micrOS/source/Config.py +20 -1
- micrOS/source/LM_buzzer.py +15 -7
- micrOS/source/LM_genIO.py +1 -1
- micrOS/source/LM_neoeffects.py +22 -7
- micrOS/source/LM_neopixel.py +1 -1
- micrOS/source/LM_ph_sensor.py +2 -2
- micrOS/source/LM_presence.py +1 -1
- micrOS/source/LM_system.py +14 -3
- micrOS/source/LM_telegram.py +226 -15
- micrOS/source/Logger.py +6 -0
- micrOS/source/Notify.py +46 -217
- micrOS/source/Shell.py +4 -3
- micrOS/source/Types.py +6 -2
- micrOS/source/Web.py +16 -3
- micrOS/source/micrOSloader.py +16 -13
- {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/METADATA +20 -7
- {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/RECORD +42 -42
- toolkit/DevEnvUSB.py +2 -1
- toolkit/MicrOSDevEnv.py +8 -5
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/LM_buzzer.mpy +0 -0
- toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
- toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
- toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
- toolkit/workspace/precompiled/LM_ph_sensor.py +2 -2
- toolkit/workspace/precompiled/LM_presence.mpy +0 -0
- toolkit/workspace/precompiled/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Notify.mpy +0 -0
- toolkit/workspace/precompiled/Shell.mpy +0 -0
- toolkit/workspace/precompiled/Types.mpy +0 -0
- toolkit/workspace/precompiled/Web.mpy +0 -0
- toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
- toolkit/workspace/precompiled/node_config.json +1 -1
- {micrOSDevToolKit-2.8.7.data → micrOSDevToolKit-2.9.1.data}/scripts/devToolKit.py +0 -0
- {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/LICENSE +0 -0
- {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/WHEEL +0 -0
- {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/top_level.txt +0 -0
micrOS/source/LM_telegram.py
CHANGED
|
@@ -1,7 +1,229 @@
|
|
|
1
|
+
from sys import modules
|
|
1
2
|
import uasyncio as asyncio
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
import urequests
|
|
4
|
+
from Notify import Notify
|
|
5
|
+
from Config import cfgget
|
|
6
|
+
from Common import micro_task, syslog, console_write
|
|
7
|
+
from LM_system import ifconfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Telegram(Notify):
|
|
11
|
+
# Telegram bot token and chat ID
|
|
12
|
+
# https://core.telegram.org/bots/api
|
|
13
|
+
_TOKEN = None # Telegram token
|
|
14
|
+
_CHAT_IDS = set() # Telegram bot chat IDs - multi group support - persistent caching
|
|
15
|
+
_API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
|
|
16
|
+
_IN_MSG_ID = None
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def __id_cache(mode):
|
|
20
|
+
"""
|
|
21
|
+
pds - persistent data structure
|
|
22
|
+
modes:
|
|
23
|
+
r - recover, s - save
|
|
24
|
+
"""
|
|
25
|
+
if mode == 's':
|
|
26
|
+
# SAVE CACHE
|
|
27
|
+
console_write("[NTFY] Save chatIDs cache...")
|
|
28
|
+
with open('telegram.pds', 'w') as f:
|
|
29
|
+
f.write(','.join([str(k) for k in Telegram._CHAT_IDS]))
|
|
30
|
+
return
|
|
31
|
+
try:
|
|
32
|
+
# RESTORE CACHE
|
|
33
|
+
console_write("[NTFY] Restore chatIDs cache...")
|
|
34
|
+
with open('telegram.pds', 'r') as f:
|
|
35
|
+
# set() comprehension
|
|
36
|
+
Telegram._CHAT_IDS = {int(k) for k in f.read().strip().split(',')}
|
|
37
|
+
except:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def __bot_token():
|
|
42
|
+
"""Get bot token"""
|
|
43
|
+
if Telegram._TOKEN is None:
|
|
44
|
+
token = cfgget('telegram')
|
|
45
|
+
if token is None or token == 'n/a':
|
|
46
|
+
return None
|
|
47
|
+
Telegram._TOKEN = token
|
|
48
|
+
return Telegram._TOKEN
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def send_msg(text, reply_to=None, chat_id=None):
|
|
52
|
+
"""
|
|
53
|
+
Send a message to the Telegram chat by chat_id
|
|
54
|
+
:param text: text to send
|
|
55
|
+
:param reply_to: reply to specific message, if None, simple reply
|
|
56
|
+
:param chat_id: chat_id to reply on, if None, reply to all known
|
|
57
|
+
RETURN None when telegram bot token is missing
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def _send(chid):
|
|
61
|
+
"""Send message to chat_id (chid)"""
|
|
62
|
+
data = {"chat_id": chid, "text": f"{Telegram._DEVFID}⚙️\n{text}"}
|
|
63
|
+
if isinstance(reply_to, int):
|
|
64
|
+
data['reply_to_message_id'] = reply_to
|
|
65
|
+
Telegram._IN_MSG_ID = reply_to
|
|
66
|
+
_, _resp = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True,
|
|
67
|
+
sock_size=128)
|
|
68
|
+
console_write(f"\tSend message:\n{data}\nresponse:\n{_resp}")
|
|
69
|
+
return _resp
|
|
70
|
+
|
|
71
|
+
def _get_chat_ids():
|
|
72
|
+
"""Return chat ID or None (in case of no token or cannot get ID)"""
|
|
73
|
+
if len(Telegram._CHAT_IDS) == 0:
|
|
74
|
+
Telegram.get_msg() # It will update the Telegram.CHAT_IDS
|
|
75
|
+
console_write(f"\tGet chatIDs: {Telegram._CHAT_IDS}")
|
|
76
|
+
return Telegram._CHAT_IDS
|
|
77
|
+
|
|
78
|
+
# --------------------- FUNCTION MAIN ------------------------ #
|
|
79
|
+
console_write("[NTFY] SEND MESSAGE")
|
|
80
|
+
# Check bot token
|
|
81
|
+
bot_token = Telegram.__bot_token()
|
|
82
|
+
if bot_token is None:
|
|
83
|
+
return None
|
|
84
|
+
url = f"https://api.telegram.org/bot{bot_token}/sendMessage{Telegram._API_PARAMS}"
|
|
85
|
+
|
|
86
|
+
verdict = ""
|
|
87
|
+
# Reply to ALL (notification) - chat_id was not provided
|
|
88
|
+
if chat_id is None:
|
|
89
|
+
console_write("\tREPLY ALL")
|
|
90
|
+
for _chat_id in _get_chat_ids():
|
|
91
|
+
resp_json = _send(chid=_chat_id)
|
|
92
|
+
verdict += f'Sent{_chat_id};' if resp_json['ok'] else str(resp_json)
|
|
93
|
+
else:
|
|
94
|
+
console_write(f"\tREPLY TO {chat_id}")
|
|
95
|
+
# Direct reply to chat_id
|
|
96
|
+
resp_json = _send(chid=chat_id)
|
|
97
|
+
verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
|
|
98
|
+
return verdict
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def get_msg():
|
|
102
|
+
"""
|
|
103
|
+
Get the last message from the Telegram chat.
|
|
104
|
+
RETURN None when telegram bot token is missing
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def _update_chat_ids():
|
|
108
|
+
"""
|
|
109
|
+
Update known chat_id-s and cache them
|
|
110
|
+
- return active chat_id frm resp_json
|
|
111
|
+
"""
|
|
112
|
+
console_write("[NTFY GET] update chatIDs")
|
|
113
|
+
_cid = None
|
|
114
|
+
if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
|
|
115
|
+
_cid = resp_json["result"][-1]["message"]["chat"]["id"]
|
|
116
|
+
# LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
|
|
117
|
+
if len(Telegram._CHAT_IDS) < 4:
|
|
118
|
+
_ids = len(Telegram._CHAT_IDS)
|
|
119
|
+
Telegram._CHAT_IDS.add(_cid)
|
|
120
|
+
if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
|
|
121
|
+
Telegram.__id_cache('s')
|
|
122
|
+
else:
|
|
123
|
+
Telegram.__id_cache('r')
|
|
124
|
+
if len(Telegram._CHAT_IDS) == 0:
|
|
125
|
+
error_message = resp_json.get("description", "Unknown error")
|
|
126
|
+
raise Exception(f"Error retrieving chat ID: {error_message}")
|
|
127
|
+
return _cid
|
|
128
|
+
|
|
129
|
+
# --------------------- FUNCTION MAIN ------------------------ #
|
|
130
|
+
console_write("[NTFY] GET MESSAGE")
|
|
131
|
+
bot_token = Telegram.__bot_token()
|
|
132
|
+
if bot_token is None:
|
|
133
|
+
return None
|
|
134
|
+
response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
|
|
135
|
+
url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
|
|
136
|
+
console_write(f"\t1/2[GET] request: {url}")
|
|
137
|
+
_, resp_json = urequests.get(url, jsonify=True, sock_size=128)
|
|
138
|
+
if len(resp_json["result"]) > 0:
|
|
139
|
+
response['c_id'] = _update_chat_ids()
|
|
140
|
+
resp = resp_json["result"][-1]["message"]
|
|
141
|
+
response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get(
|
|
142
|
+
'username', None) is None else resp['chat']['username']
|
|
143
|
+
response['text'], response['m_id'] = resp['text'], resp['message_id']
|
|
144
|
+
console_write(f"\t2/2[GET] response: {response}")
|
|
145
|
+
return response
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def receive_eval():
|
|
149
|
+
"""
|
|
150
|
+
READ - VALIDATE - EXECUTE - REPLY LOOP
|
|
151
|
+
- can be used in async loop
|
|
152
|
+
RETURN None when telegram bot token is missing
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
console_write("[NTFY] REC&EVAL sequence")
|
|
156
|
+
|
|
157
|
+
# Return data structure template
|
|
158
|
+
verdict = None
|
|
159
|
+
|
|
160
|
+
def lm_execute(cmd_args):
|
|
161
|
+
nonlocal verdict, m_id
|
|
162
|
+
access, output = Telegram.lm_execute(cmd_args)
|
|
163
|
+
if access:
|
|
164
|
+
verdict = f'[UP] Exec: {" ".join(cmd_args[0])}'
|
|
165
|
+
Telegram.send_msg(output, reply_to=m_id)
|
|
166
|
+
else:
|
|
167
|
+
verdict = f'[UP] NoAccess: {cmd_args[0]}'
|
|
168
|
+
Telegram._IN_MSG_ID = m_id
|
|
169
|
+
|
|
170
|
+
# -------------------------- FUNCTION MAIN -------------------------- #
|
|
171
|
+
# Poll telegram chat
|
|
172
|
+
data = Telegram.get_msg()
|
|
173
|
+
if data is None:
|
|
174
|
+
return data
|
|
175
|
+
# Get msg, msg_id, chat_id as main input data source
|
|
176
|
+
msg_in, m_id, c_id = data['text'], data['m_id'], data['c_id']
|
|
177
|
+
if msg_in is not None and m_id != Telegram._IN_MSG_ID:
|
|
178
|
+
# replace single/double quotation to apostrophe (str syntax for repl interpretation)
|
|
179
|
+
msg_in = msg_in.replace('‘', "'").replace('’', "'").replace('“', '"').replace('”', '"')
|
|
180
|
+
if msg_in.startswith('/ping'):
|
|
181
|
+
# Parse loaded modules
|
|
182
|
+
_loaded_mods = [lm.replace('LM_', '') for lm in modules if lm.startswith('LM_')] + ['task']
|
|
183
|
+
Telegram.send_msg(', '.join(_loaded_mods), reply_to=m_id, chat_id=c_id)
|
|
184
|
+
elif msg_in.startswith('/cmd_select'):
|
|
185
|
+
cmd_lm = msg_in.strip().split()[1:]
|
|
186
|
+
# [Compare] cmd selected device param with DEVFID (device/prompt name)
|
|
187
|
+
if cmd_lm[0] in Telegram._DEVFID:
|
|
188
|
+
lm_execute(cmd_lm[1:])
|
|
189
|
+
else:
|
|
190
|
+
verdict = f'[UP] NoSelected: {cmd_lm[0]}'
|
|
191
|
+
elif msg_in.startswith('/cmd'):
|
|
192
|
+
cmd_lm = msg_in.strip().split()[1:]
|
|
193
|
+
lm_execute(cmd_lm)
|
|
194
|
+
elif msg_in.startswith('/notify'):
|
|
195
|
+
param = msg_in.strip().split()[1:]
|
|
196
|
+
if len(param) > 0:
|
|
197
|
+
verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
|
|
198
|
+
else:
|
|
199
|
+
verdict = Telegram.notifications()
|
|
200
|
+
Telegram.send_msg(verdict, reply_to=m_id)
|
|
201
|
+
else:
|
|
202
|
+
verdict = "[UP] NoExec"
|
|
203
|
+
console_write(f"\tREC&EVAL: {verdict}")
|
|
204
|
+
return verdict
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def set_commands():
|
|
208
|
+
"""
|
|
209
|
+
Set Custom Commands to the Telegram chat.
|
|
210
|
+
RETURN None when telegram bot token is missing
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
console_write("[NTFY] SET DEFAULT COMMANDS")
|
|
214
|
+
bot_token = Telegram.__bot_token()
|
|
215
|
+
if bot_token is None:
|
|
216
|
+
return None
|
|
217
|
+
url = f"https://api.telegram.org/bot{bot_token}/setMyCommands{Telegram._API_PARAMS}"
|
|
218
|
+
data = {"commands": [{"command": "ping", "description": "Ping All endpoints: list of active modules."},
|
|
219
|
+
{"command": "notify", "description": "Enable/Disable notifications: on or off"},
|
|
220
|
+
{"command": "cmd", "description": "Run active module function on all devices."},
|
|
221
|
+
{"command": "cmd_select",
|
|
222
|
+
"description": "Same as cmd, only first param must be device name."},
|
|
223
|
+
]}
|
|
224
|
+
_, resp_json = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True,
|
|
225
|
+
sock_size=128)
|
|
226
|
+
return 'Custom commands was set' if resp_json['ok'] else str(resp_json)
|
|
5
227
|
|
|
6
228
|
#########################################
|
|
7
229
|
# micrOS Notifications #
|
|
@@ -15,6 +237,7 @@ def __init():
|
|
|
15
237
|
_sta_available = True if ifconfig()[0] == "STA" else False
|
|
16
238
|
if _sta_available:
|
|
17
239
|
TELEGRAM_OBJ = Telegram()
|
|
240
|
+
Notify.add_subscriber(TELEGRAM_OBJ)
|
|
18
241
|
else:
|
|
19
242
|
syslog("No STA: cannot init telegram")
|
|
20
243
|
|
|
@@ -103,17 +326,6 @@ def receiver_loop():
|
|
|
103
326
|
return "Starting" if state else "Already running"
|
|
104
327
|
|
|
105
328
|
|
|
106
|
-
def notifications(enable=None):
|
|
107
|
-
"""
|
|
108
|
-
Global notifications control for micrOS
|
|
109
|
-
:param enable: True: Enable notifications / False: Disable notifications
|
|
110
|
-
return: state verdict
|
|
111
|
-
"""
|
|
112
|
-
if enable is None:
|
|
113
|
-
enable = not Telegram.GLOBAL_NOTIFY
|
|
114
|
-
return Telegram.notifications(state=enable)
|
|
115
|
-
|
|
116
|
-
|
|
117
329
|
def help(widgets=False):
|
|
118
330
|
"""
|
|
119
331
|
[i] micrOS LM naming convention - built-in help message
|
|
@@ -125,5 +337,4 @@ def help(widgets=False):
|
|
|
125
337
|
'receive',
|
|
126
338
|
'receiver_loop',
|
|
127
339
|
'notify "message"',
|
|
128
|
-
'notifications enable=False',
|
|
129
340
|
'load', 'INFO: Send & Receive messages with Telegram bot')
|
micrOS/source/Logger.py
CHANGED
micrOS/source/Notify.py
CHANGED
|
@@ -1,247 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Module is responsible for Notification handling
|
|
3
|
+
Common:
|
|
4
|
+
- notifications None/True/False
|
|
5
|
+
- notify 'text'
|
|
6
|
+
- lm_execute
|
|
7
|
+
Supported notification subscribers (add_subscriber)
|
|
8
|
+
- LM_telegram
|
|
9
|
+
Designed by Marcell Ban aka BxNxM
|
|
10
|
+
"""
|
|
11
|
+
|
|
3
12
|
from Config import cfgget
|
|
4
13
|
from Tasks import lm_exec, lm_is_loaded
|
|
5
|
-
from Debug import
|
|
14
|
+
from Debug import errlog_add
|
|
6
15
|
|
|
7
16
|
#########################################
|
|
8
17
|
# micrOS Notifications #
|
|
9
18
|
# with Telegram Class #
|
|
10
19
|
#########################################
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
GLOBAL_NOTIFY = True # Enable Global notifications
|
|
16
|
-
_TOKEN = None # Telegram token
|
|
17
|
-
_CHAT_IDS = set() # Telegram bot chat IDs - multi group support - persistent caching
|
|
18
|
-
_API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
|
|
19
|
-
_DEVFID = cfgget('devfid') # For reply message (pre text)
|
|
20
|
-
_IN_MSG_ID = None
|
|
20
|
+
class Notify:
|
|
21
|
+
GLOBAL_NOTIFY = True # Enable Global notifications
|
|
22
|
+
_DEVFID = cfgget('devfid') # For reply message (pre text)
|
|
23
|
+
_SUBSCRIBERS = set() # Store set of notification objects: send_msg
|
|
21
24
|
|
|
22
25
|
@staticmethod
|
|
23
|
-
def
|
|
26
|
+
def add_subscriber(instance):
|
|
24
27
|
"""
|
|
25
|
-
|
|
26
|
-
modes:
|
|
27
|
-
r - recover, s - save
|
|
28
|
+
Add Notification agent like: Telegram
|
|
28
29
|
"""
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
f.write(','.join([str(k) for k in Telegram._CHAT_IDS]))
|
|
34
|
-
return
|
|
35
|
-
try:
|
|
36
|
-
# RESTORE CACHE
|
|
37
|
-
console_write("[NTFY] Restore chatIDs cache...")
|
|
38
|
-
with open('telegram.pds', 'r') as f:
|
|
39
|
-
# set() comprehension
|
|
40
|
-
Telegram._CHAT_IDS = {int(k) for k in f.read().strip().split(',')}
|
|
41
|
-
except:
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
@staticmethod
|
|
45
|
-
def __bot_token():
|
|
46
|
-
"""Get bot token"""
|
|
47
|
-
if Telegram._TOKEN is None:
|
|
48
|
-
token = cfgget('telegram')
|
|
49
|
-
if token is None or token == 'n/a':
|
|
50
|
-
return None
|
|
51
|
-
Telegram._TOKEN = token
|
|
52
|
-
return Telegram._TOKEN
|
|
30
|
+
if isinstance(instance, Notify):
|
|
31
|
+
Notify._SUBSCRIBERS.add(instance)
|
|
32
|
+
return True
|
|
33
|
+
raise Exception("Subscribe error, Notify parent missing")
|
|
53
34
|
|
|
54
35
|
@staticmethod
|
|
55
|
-
def
|
|
36
|
+
def message(text, reply_to=None, chat_id=None):
|
|
56
37
|
"""
|
|
57
|
-
Send
|
|
58
|
-
:param text: text to send
|
|
59
|
-
:param reply_to: reply to specific message, if None, simple reply
|
|
60
|
-
:param chat_id: chat_id to reply on, if None, reply to all known
|
|
61
|
-
RETURN None when telegram bot token is missing
|
|
38
|
+
Send message to all subscribers
|
|
62
39
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return _resp
|
|
72
|
-
|
|
73
|
-
def _get_chat_ids():
|
|
74
|
-
"""Return chat ID or None (in case of no token or cannot get ID)"""
|
|
75
|
-
if len(Telegram._CHAT_IDS) == 0:
|
|
76
|
-
Telegram.get_msg() # It will update the Telegram.CHAT_IDS
|
|
77
|
-
console_write(f"\tGet chatIDs: {Telegram._CHAT_IDS}")
|
|
78
|
-
return Telegram._CHAT_IDS
|
|
79
|
-
|
|
80
|
-
# --------------------- FUNCTION MAIN ------------------------ #
|
|
81
|
-
console_write("[NTFY] SEND MESSAGE")
|
|
82
|
-
# Check bot token
|
|
83
|
-
bot_token = Telegram.__bot_token()
|
|
84
|
-
if bot_token is None:
|
|
85
|
-
return None
|
|
86
|
-
url = f"https://api.telegram.org/bot{bot_token}/sendMessage{Telegram._API_PARAMS}"
|
|
87
|
-
|
|
88
|
-
verdict = ""
|
|
89
|
-
# Reply to ALL (notification) - chat_id was not provided
|
|
90
|
-
if chat_id is None:
|
|
91
|
-
console_write("\tREPLY ALL")
|
|
92
|
-
for _chat_id in _get_chat_ids():
|
|
93
|
-
resp_json = _send(chid=_chat_id)
|
|
94
|
-
verdict += f'Sent{_chat_id};' if resp_json['ok'] else str(resp_json)
|
|
95
|
-
else:
|
|
96
|
-
console_write(f"\tREPLY TO {chat_id}")
|
|
97
|
-
# Direct reply to chat_id
|
|
98
|
-
resp_json = _send(chid=chat_id)
|
|
99
|
-
verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
|
|
100
|
-
return verdict
|
|
40
|
+
exit_code = 0
|
|
41
|
+
for s in Notify._SUBSCRIBERS:
|
|
42
|
+
try:
|
|
43
|
+
s.send_msg(text, reply_to, chat_id)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
errlog_add(f"[ERR] Notify: {e}")
|
|
46
|
+
exit_code+=1
|
|
47
|
+
return f"Sent N{len(Notify._SUBSCRIBERS)} ({exit_code})"
|
|
101
48
|
|
|
102
49
|
@staticmethod
|
|
103
50
|
def notifications(state=None):
|
|
104
51
|
"""
|
|
105
52
|
Setter for disable/enable notification messages
|
|
106
53
|
"""
|
|
107
|
-
if state
|
|
108
|
-
|
|
109
|
-
return f"Notifications: {'enabled' if
|
|
110
|
-
|
|
54
|
+
if isinstance(state, bool):
|
|
55
|
+
Notify.GLOBAL_NOTIFY = state
|
|
56
|
+
return f"Notifications: {'enabled' if Notify.GLOBAL_NOTIFY else 'disabled'}"
|
|
111
57
|
|
|
112
58
|
@staticmethod
|
|
113
59
|
def notify(text, reply_to=None, chat_id=None):
|
|
114
60
|
"""
|
|
115
61
|
Notification sender
|
|
116
62
|
"""
|
|
117
|
-
if
|
|
118
|
-
return
|
|
63
|
+
if Notify.GLOBAL_NOTIFY:
|
|
64
|
+
return Notify.message(text, reply_to, chat_id)
|
|
119
65
|
return "Notifications disabled"
|
|
120
66
|
|
|
121
67
|
@staticmethod
|
|
122
|
-
def
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- return active chat_id frm resp_json
|
|
132
|
-
"""
|
|
133
|
-
console_write("[NTFY GET] update chatIDs")
|
|
134
|
-
_cid = None
|
|
135
|
-
if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
|
|
136
|
-
_cid = resp_json["result"][-1]["message"]["chat"]["id"]
|
|
137
|
-
# LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
|
|
138
|
-
if len(Telegram._CHAT_IDS) < 4:
|
|
139
|
-
_ids = len(Telegram._CHAT_IDS)
|
|
140
|
-
Telegram._CHAT_IDS.add(_cid)
|
|
141
|
-
if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
|
|
142
|
-
Telegram.__id_cache('s')
|
|
143
|
-
else:
|
|
144
|
-
Telegram.__id_cache('r')
|
|
145
|
-
if len(Telegram._CHAT_IDS) == 0:
|
|
146
|
-
error_message = resp_json.get("description", "Unknown error")
|
|
147
|
-
raise Exception(f"Error retrieving chat ID: {error_message}")
|
|
148
|
-
return _cid
|
|
149
|
-
|
|
150
|
-
# --------------------- FUNCTION MAIN ------------------------ #
|
|
151
|
-
console_write("[NTFY] GET MESSAGE")
|
|
152
|
-
bot_token = Telegram.__bot_token()
|
|
153
|
-
if bot_token is None:
|
|
154
|
-
return None
|
|
155
|
-
response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
|
|
156
|
-
url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
|
|
157
|
-
console_write(f"\t1/2[GET] request: {url}")
|
|
158
|
-
_, resp_json = urequests.get(url, jsonify=True, sock_size=128)
|
|
159
|
-
if len(resp_json["result"]) > 0:
|
|
160
|
-
response['c_id'] = _update_chat_ids()
|
|
161
|
-
resp = resp_json["result"][-1]["message"]
|
|
162
|
-
response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get('username', None) is None else resp['chat']['username']
|
|
163
|
-
response['text'], response['m_id'] = resp['text'], resp['message_id']
|
|
164
|
-
console_write(f"\t2/2[GET] response: {response}")
|
|
165
|
-
return response
|
|
166
|
-
|
|
167
|
-
@staticmethod
|
|
168
|
-
def receive_eval():
|
|
169
|
-
"""
|
|
170
|
-
READ - VALIDATE - EXECUTE - REPLY LOOP
|
|
171
|
-
- can be used in async loop
|
|
172
|
-
RETURN None when telegram bot token is missing
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
console_write("[NTFY] REC&EVAL sequence")
|
|
176
|
-
|
|
177
|
-
# Return data structure template
|
|
178
|
-
verdict = None
|
|
179
|
-
|
|
180
|
-
def lm_execute(cmd_args):
|
|
181
|
-
nonlocal verdict
|
|
182
|
-
if lm_is_loaded(cmd_args[0]):
|
|
183
|
-
verdict = f'[UP] Exec: {" ".join(cmd_args[0])}'
|
|
184
|
-
try:
|
|
185
|
-
_, out = lm_exec(cmd_args)
|
|
186
|
-
except Exception as e:
|
|
187
|
-
out = str(e)
|
|
188
|
-
Telegram.send_msg(out, reply_to=m_id)
|
|
189
|
-
else:
|
|
190
|
-
verdict = f'[UP] NoAccess: {cmd_args[0]}'
|
|
191
|
-
Telegram._IN_MSG_ID = m_id
|
|
192
|
-
|
|
193
|
-
# -------------------------- FUNCTION MAIN -------------------------- #
|
|
194
|
-
# Poll telegram chat
|
|
195
|
-
data = Telegram.get_msg()
|
|
196
|
-
if data is None:
|
|
197
|
-
return data
|
|
198
|
-
# Get msg, msg_id, chat_id as main input data source
|
|
199
|
-
msg_in, m_id, c_id = data['text'], data['m_id'], data['c_id']
|
|
200
|
-
if msg_in is not None and m_id != Telegram._IN_MSG_ID:
|
|
201
|
-
# replace single/double quotation to apostrophe (str syntax for repl interpretation)
|
|
202
|
-
msg_in = msg_in.replace('‘', "'").replace('’', "'").replace('“', '"').replace('”', '"')
|
|
203
|
-
if msg_in.startswith('/ping'):
|
|
204
|
-
# Parse loaded modules
|
|
205
|
-
_loaded_mods = [lm.replace('LM_', '') for lm in modules if lm.startswith('LM_')] + ['task']
|
|
206
|
-
Telegram.send_msg(', '.join(_loaded_mods), reply_to=m_id, chat_id=c_id)
|
|
207
|
-
elif msg_in.startswith('/cmd_select'):
|
|
208
|
-
cmd_lm = msg_in.strip().split()[1:]
|
|
209
|
-
# [Compare] cmd selected device param with DEVFID (device/prompt name)
|
|
210
|
-
if cmd_lm[0] in Telegram._DEVFID:
|
|
211
|
-
lm_execute(cmd_lm[1:])
|
|
212
|
-
else:
|
|
213
|
-
verdict = f'[UP] NoSelected: {cmd_lm[0]}'
|
|
214
|
-
elif msg_in.startswith('/cmd'):
|
|
215
|
-
cmd_lm = msg_in.strip().split()[1:]
|
|
216
|
-
lm_execute(cmd_lm)
|
|
217
|
-
elif msg_in.startswith('/notify'):
|
|
218
|
-
param = msg_in.strip().split()[1:]
|
|
219
|
-
if len(param) > 0:
|
|
220
|
-
verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
|
|
221
|
-
else:
|
|
222
|
-
verdict = Telegram.notifications()
|
|
223
|
-
Telegram.send_msg(verdict, reply_to=m_id)
|
|
224
|
-
else:
|
|
225
|
-
verdict = "[UP] NoExec"
|
|
226
|
-
console_write(f"\tREC&EVAL: {verdict}")
|
|
227
|
-
return verdict
|
|
228
|
-
|
|
229
|
-
@staticmethod
|
|
230
|
-
def set_commands():
|
|
231
|
-
"""
|
|
232
|
-
Set Custom Commands to the Telegram chat.
|
|
233
|
-
RETURN None when telegram bot token is missing
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
console_write("[NTFY] SET DEFAULT COMMANDS")
|
|
237
|
-
bot_token = Telegram.__bot_token()
|
|
238
|
-
if bot_token is None:
|
|
239
|
-
return None
|
|
240
|
-
url = f"https://api.telegram.org/bot{bot_token}/setMyCommands{Telegram._API_PARAMS}"
|
|
241
|
-
data = {"commands": [{"command": "ping", "description": "Ping All endpoints: list of active modules."},
|
|
242
|
-
{"command": "notify", "description": "Enable/Disable notifications: on or off"},
|
|
243
|
-
{"command": "cmd", "description": "Run active module function on all devices."},
|
|
244
|
-
{"command": "cmd_select", "description": "Same as cmd, only first param must be device name."},
|
|
245
|
-
]}
|
|
246
|
-
_, resp_json = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True, sock_size=128)
|
|
247
|
-
return 'Custom commands was set' if resp_json['ok'] else str(resp_json)
|
|
68
|
+
def lm_execute(cmd_args):
|
|
69
|
+
"""Executor with basic access handling"""
|
|
70
|
+
if lm_is_loaded(cmd_args[0]):
|
|
71
|
+
try:
|
|
72
|
+
_, out = lm_exec(cmd_args)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
out = str(e)
|
|
75
|
+
return True, out
|
|
76
|
+
return False, cmd_args[0]
|
micrOS/source/Shell.py
CHANGED
|
@@ -25,7 +25,7 @@ from Debug import errlog_add
|
|
|
25
25
|
|
|
26
26
|
class Shell:
|
|
27
27
|
__slots__ = ['__devfid', '__auth_mode', '__hwuid', '__auth_ok', '__conf_mode']
|
|
28
|
-
MICROS_VERSION = '2.
|
|
28
|
+
MICROS_VERSION = '2.9.1-0'
|
|
29
29
|
|
|
30
30
|
def __init__(self):
|
|
31
31
|
"""
|
|
@@ -264,8 +264,9 @@ class Shell:
|
|
|
264
264
|
line = "micrOSisTheBest"
|
|
265
265
|
while line:
|
|
266
266
|
line = f.readline()
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
ldata = line.strip()
|
|
268
|
+
if ldata.startswith('def ') and not ldata.split()[1].startswith("_") and 'self' not in ldata:
|
|
269
|
+
msg_obj(f" {' ' * len(lm_name)}{ldata.replace('def ', '').split('(')[0]}")
|
|
269
270
|
except Exception as e:
|
|
270
271
|
msg_obj(f"[{lm_path}] SHOW LM PARSER WARNING: {e}")
|
|
271
272
|
return False
|
micrOS/source/Types.py
CHANGED
|
@@ -70,8 +70,12 @@ def _generate(type_dict, help_msg):
|
|
|
70
70
|
overwrite[value_type] = values
|
|
71
71
|
valid_params.append(param_str)
|
|
72
72
|
else:
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if p.startswith("&"):
|
|
74
|
+
# Handle special use case - task postfix
|
|
75
|
+
valid_params.append(p)
|
|
76
|
+
else:
|
|
77
|
+
param_str = f'{p}=:{"range" if "range" in type_dict else "options"}:'
|
|
78
|
+
valid_params.append(param_str)
|
|
75
79
|
type_dict['lm_call'] = f"{func} {' '.join(valid_params)}"
|
|
76
80
|
return dumps(type_dict | overwrite)
|
|
77
81
|
|
micrOS/source/Web.py
CHANGED
|
@@ -74,9 +74,22 @@ class WebEngine:
|
|
|
74
74
|
try:
|
|
75
75
|
# SEND RESOURCE CONTENT: HTML, JS, CSS
|
|
76
76
|
with open(resource, 'r') as file:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
data = file.read()
|
|
78
|
+
response = self.REQ200.format(dtype=WebEngine.file_type(resource), len=len(data), data=data)
|
|
79
|
+
# Send entire response data
|
|
80
|
+
await self.client.a_send(response)
|
|
81
|
+
|
|
82
|
+
# Send chunks of response data (experimental)
|
|
83
|
+
#response_len, chunk_size, position = len(response), 300, 0
|
|
84
|
+
#while position < response_len:
|
|
85
|
+
# # Calculate the size of the next chunk
|
|
86
|
+
# next_chunk_size = min(chunk_size, response_len - position)
|
|
87
|
+
# chunk = response[position:position + next_chunk_size]
|
|
88
|
+
# await self.client.a_send(chunk)
|
|
89
|
+
# position += next_chunk_size
|
|
90
|
+
except Exception as e:
|
|
91
|
+
if 'memory allocation failed' in str(e):
|
|
92
|
+
errlog_add(f"[ERR] WebCli {resource}: {e}")
|
|
80
93
|
await self.client.a_send(self.REQ404.format(len=13, data='404 Not Found'))
|
|
81
94
|
return
|
|
82
95
|
# INVALID/BAD REQUEST
|