lockbot 2.8.0__tar.gz → 2.9.0__tar.gz
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.
- {lockbot-2.8.0/python/lockbot.egg-info → lockbot-2.9.0}/PKG-INFO +1 -1
- {lockbot-2.8.0 → lockbot-2.9.0}/pyproject.toml +1 -1
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/router.py +34 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/schemas.py +11 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/base_bot.py +12 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_bot.py +5 -3
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_usage_utils.py +3 -2
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/node_bot.py +7 -6
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/queue_bot.py +3 -3
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/utils.py +2 -1
- {lockbot-2.8.0 → lockbot-2.9.0/python/lockbot.egg-info}/PKG-INFO +1 -1
- {lockbot-2.8.0 → lockbot-2.9.0}/LICENSE +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/MANIFEST.in +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/README.md +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/admin/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/admin/router.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/models.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/router.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/service.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/dependencies.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/models.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/router.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/schemas.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/router.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/scheduler.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/service.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/encryption.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/manager.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/models.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/config.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/database.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/logs/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/main.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/rate_limit.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/models.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/router.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/bot_instance.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/config.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_usage_alert.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/entry.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/env.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/handler.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/en.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/zh.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/io.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/message_adapter.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/msg_utils.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/platforms/__init__.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/platforms/infoflow.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/request.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/scheduler.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/SOURCES.txt +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/dependency_links.txt +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/requires.txt +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/top_level.txt +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/setup.cfg +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/tools/create_super_admin.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/tools/gen_keys.py +0 -0
- {lockbot-2.8.0 → lockbot-2.9.0}/tools/reset_super_admin_password.py +0 -0
|
@@ -127,6 +127,33 @@ def _normalize_cluster_configs(cc):
|
|
|
127
127
|
return cc
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def _get_node_keys(cc) -> set:
|
|
131
|
+
"""Extract node key set from cluster_configs (any format)."""
|
|
132
|
+
if _is_device_array_format(cc):
|
|
133
|
+
return {item["node_key"] for item in cc}
|
|
134
|
+
if isinstance(cc, dict):
|
|
135
|
+
return set(cc.keys())
|
|
136
|
+
if isinstance(cc, list):
|
|
137
|
+
return set(cc)
|
|
138
|
+
return set()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _validate_node_aliases(config_overrides, cluster_configs):
|
|
142
|
+
"""Raise HTTPException if NODE_ALIASES contains keys not in cluster_configs."""
|
|
143
|
+
if not config_overrides:
|
|
144
|
+
return
|
|
145
|
+
aliases = config_overrides.get("NODE_ALIASES")
|
|
146
|
+
if not aliases:
|
|
147
|
+
return
|
|
148
|
+
valid_keys = _get_node_keys(cluster_configs)
|
|
149
|
+
invalid = set(aliases.keys()) - valid_keys
|
|
150
|
+
if invalid:
|
|
151
|
+
raise HTTPException(
|
|
152
|
+
status_code=422,
|
|
153
|
+
detail=f"NODE_ALIASES contains keys not in cluster_configs: {sorted(invalid)}",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
130
157
|
def _device_dict_to_array(d: dict) -> list:
|
|
131
158
|
"""Convert DEVICE cluster_configs from dict to ordered array for API response.
|
|
132
159
|
|
|
@@ -214,6 +241,8 @@ def create_bot(
|
|
|
214
241
|
if body.bot_type.upper() not in VALID_BOT_TYPES:
|
|
215
242
|
raise HTTPException(status_code=422, detail=f"Invalid bot_type, must be one of {VALID_BOT_TYPES}")
|
|
216
243
|
|
|
244
|
+
_validate_node_aliases(body.config_overrides, body.cluster_configs)
|
|
245
|
+
|
|
217
246
|
exists = (
|
|
218
247
|
db.query(Bot)
|
|
219
248
|
.filter(
|
|
@@ -349,6 +378,11 @@ def update_bot(
|
|
|
349
378
|
if user.role != "super_admin" and bot.user_id != user.id:
|
|
350
379
|
raise HTTPException(status_code=403, detail="Cannot edit another user's bot")
|
|
351
380
|
|
|
381
|
+
# Validate NODE_ALIASES keys against cluster_configs
|
|
382
|
+
if body.config_overrides and body.config_overrides.get("NODE_ALIASES"):
|
|
383
|
+
cc = body.cluster_configs if body.cluster_configs is not None else json.loads(bot.cluster_configs)
|
|
384
|
+
_validate_node_aliases(body.config_overrides, cc)
|
|
385
|
+
|
|
352
386
|
changes = {}
|
|
353
387
|
if body.name is not None:
|
|
354
388
|
# Check for duplicate name (excluding current bot and deleted bots)
|
|
@@ -57,6 +57,17 @@ def _validate_config_overrides(v: dict | None) -> dict | None:
|
|
|
57
57
|
if whitelist is not None and (not isinstance(whitelist, list) or not all(isinstance(u, str) for u in whitelist)):
|
|
58
58
|
errors.append("DURATION_WHITELIST must be a list of strings")
|
|
59
59
|
|
|
60
|
+
node_aliases = v.get("NODE_ALIASES")
|
|
61
|
+
if node_aliases is not None and (
|
|
62
|
+
not isinstance(node_aliases, dict)
|
|
63
|
+
or not all(isinstance(k, str) and isinstance(a, str) and len(a) <= 15 for k, a in node_aliases.items())
|
|
64
|
+
):
|
|
65
|
+
errors.append("NODE_ALIASES must be a dict of {string: string} with alias length <= 15")
|
|
66
|
+
|
|
67
|
+
show_alias = v.get("SHOW_NODE_ALIAS")
|
|
68
|
+
if show_alias is not None and not isinstance(show_alias, bool):
|
|
69
|
+
errors.append("SHOW_NODE_ALIAS must be a boolean")
|
|
70
|
+
|
|
60
71
|
max_dur = v.get("MAX_LOCK_DURATION")
|
|
61
72
|
default_dur = v.get("DEFAULT_DURATION")
|
|
62
73
|
if isinstance(max_dur, int) and isinstance(default_dur, int) and max_dur != -1 and default_dur > max_dur:
|
|
@@ -121,6 +121,18 @@ class BaseLockBot:
|
|
|
121
121
|
"""
|
|
122
122
|
return self.adapter.build_reply("\u274c" + error_msg, [user_id])
|
|
123
123
|
|
|
124
|
+
@property
|
|
125
|
+
def _duration_whitelist(self):
|
|
126
|
+
return self.config.get_val("DURATION_WHITELIST") or []
|
|
127
|
+
|
|
128
|
+
def _display_node(self, node_key):
|
|
129
|
+
"""Return node_key with alias suffix if SHOW_NODE_ALIAS is enabled."""
|
|
130
|
+
if not self.config.get_val("SHOW_NODE_ALIAS"):
|
|
131
|
+
return node_key
|
|
132
|
+
aliases = self.config.get_val("NODE_ALIASES") or {}
|
|
133
|
+
alias = aliases.get(node_key)
|
|
134
|
+
return f"{node_key}「{alias}」" if alias else node_key
|
|
135
|
+
|
|
124
136
|
def _whitelist_hint(self):
|
|
125
137
|
"""Return whitelist hint string if BOT_OWNER is configured, else empty string."""
|
|
126
138
|
owner = self.config.get_val("BOT_OWNER")
|
|
@@ -177,7 +177,7 @@ class DeviceBot(BaseLockBot):
|
|
|
177
177
|
return error_reply
|
|
178
178
|
|
|
179
179
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
180
|
-
whitelist = self.
|
|
180
|
+
whitelist = self._duration_whitelist
|
|
181
181
|
with self._lock:
|
|
182
182
|
timestamp = int(time.time())
|
|
183
183
|
|
|
@@ -251,7 +251,7 @@ class DeviceBot(BaseLockBot):
|
|
|
251
251
|
return error_reply
|
|
252
252
|
|
|
253
253
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
254
|
-
whitelist = self.
|
|
254
|
+
whitelist = self._duration_whitelist
|
|
255
255
|
with self._lock:
|
|
256
256
|
timestamp = int(time.time())
|
|
257
257
|
for node_key, dev_ids in zip(node_key_list, dev_ids_list, strict=True):
|
|
@@ -597,4 +597,6 @@ class DeviceBot(BaseLockBot):
|
|
|
597
597
|
"""
|
|
598
598
|
Get a text description of the current cluster device usage (supports merged display and heterogeneous hints).
|
|
599
599
|
"""
|
|
600
|
-
return get_current_usage(
|
|
600
|
+
return get_current_usage(
|
|
601
|
+
node_filter, self.state.bot_state, {}, config=self.config, display_node=self._display_node
|
|
602
|
+
)
|
|
@@ -157,7 +157,7 @@ def render_device_lines(node_status, grouped_usage, idle_groups, config=None):
|
|
|
157
157
|
return lines
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def get_current_usage(node_filter, bot_state, monitor_status, config=None):
|
|
160
|
+
def get_current_usage(node_filter, bot_state, monitor_status, config=None, display_node=None):
|
|
161
161
|
"""
|
|
162
162
|
Get current device usage info including idle, locked, and in-use devices.
|
|
163
163
|
"""
|
|
@@ -169,7 +169,8 @@ def get_current_usage(node_filter, bot_state, monitor_status, config=None):
|
|
|
169
169
|
or node_key == node_filter
|
|
170
170
|
or (isinstance(node_filter, list) and node_key in node_filter)
|
|
171
171
|
):
|
|
172
|
-
|
|
172
|
+
shown_name = display_node(node_key) if display_node else node_key
|
|
173
|
+
usage_info += t("device_usage.node_header", config=config, node_key=shown_name)
|
|
173
174
|
|
|
174
175
|
grouped_usage = group_locked_devices(node_status)
|
|
175
176
|
shown_indices = set()
|
|
@@ -111,7 +111,7 @@ class NodeBot(BaseLockBot):
|
|
|
111
111
|
return error_reply
|
|
112
112
|
|
|
113
113
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
114
|
-
whitelist = self.
|
|
114
|
+
whitelist = self._duration_whitelist
|
|
115
115
|
with self._lock:
|
|
116
116
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
117
117
|
if not all(
|
|
@@ -171,7 +171,7 @@ class NodeBot(BaseLockBot):
|
|
|
171
171
|
return error_reply
|
|
172
172
|
|
|
173
173
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
174
|
-
whitelist = self.
|
|
174
|
+
whitelist = self._duration_whitelist
|
|
175
175
|
with self._lock:
|
|
176
176
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
177
177
|
if not all(node["status"] != "exclusive" for node in nodes):
|
|
@@ -397,7 +397,7 @@ class NodeBot(BaseLockBot):
|
|
|
397
397
|
|
|
398
398
|
uid = user_info["user_id"] + format_access_mode(node["status"], config=self.config)
|
|
399
399
|
duration = format_duration(remaining_time, config=self.config)
|
|
400
|
-
alert_info += f"{node_key} {uid} {duration}\n"
|
|
400
|
+
alert_info += f"{self._display_node(node_key)} {uid} {duration}\n"
|
|
401
401
|
|
|
402
402
|
if EARLY_NOTIFY and not user_info["is_notified"] and 0 < remaining_time <= TIME_ALERT:
|
|
403
403
|
trigger_time_alert = True
|
|
@@ -407,7 +407,7 @@ class NodeBot(BaseLockBot):
|
|
|
407
407
|
|
|
408
408
|
uid = user_info["user_id"] + format_access_mode(node["status"], config=self.config)
|
|
409
409
|
duration = format_duration(remaining_time, config=self.config)
|
|
410
|
-
alert_info += f"{node_key} {uid} {duration}\n"
|
|
410
|
+
alert_info += f"{self._display_node(node_key)} {uid} {duration}\n"
|
|
411
411
|
|
|
412
412
|
for user_id in removed_users_id:
|
|
413
413
|
remove_user_info(node["current_users"], user_id)
|
|
@@ -453,11 +453,12 @@ class NodeBot(BaseLockBot):
|
|
|
453
453
|
usage_info = ""
|
|
454
454
|
for node_key, node_status in nodes.items():
|
|
455
455
|
if node_filter is None or node_key == node_filter:
|
|
456
|
+
display_name = self._display_node(node_key)
|
|
456
457
|
if node_status["status"] == "idle":
|
|
457
|
-
usage_info += "{:} {}\n".format(
|
|
458
|
+
usage_info += "{:} {}\n".format(display_name, t("status.idle", config=self.config))
|
|
458
459
|
else:
|
|
459
460
|
for user_idx, user_info in enumerate(node_status["current_users"]):
|
|
460
|
-
node_name = f"{
|
|
461
|
+
node_name = f"{display_name}" if user_idx == 0 else ""
|
|
461
462
|
uid = user_info["user_id"] + format_access_mode(node_status["status"], config=self.config)
|
|
462
463
|
duration = format_duration(
|
|
463
464
|
remaining_duration(user_info["start_time"], user_info["duration"]), config=self.config
|
|
@@ -38,7 +38,7 @@ class QueueBot(NodeBot):
|
|
|
38
38
|
return error_reply
|
|
39
39
|
|
|
40
40
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
41
|
-
whitelist = self.
|
|
41
|
+
whitelist = self._duration_whitelist
|
|
42
42
|
with self._lock:
|
|
43
43
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
44
44
|
if not all(
|
|
@@ -132,7 +132,7 @@ class QueueBot(NodeBot):
|
|
|
132
132
|
return error_reply
|
|
133
133
|
|
|
134
134
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
135
|
-
whitelist = self.
|
|
135
|
+
whitelist = self._duration_whitelist
|
|
136
136
|
with self._lock:
|
|
137
137
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
138
138
|
if any(
|
|
@@ -174,7 +174,7 @@ class QueueBot(NodeBot):
|
|
|
174
174
|
content = t("success.take_success_by", config=self.config, user_id=user_id)
|
|
175
175
|
content += self._msg_with_usage("label.before_take")
|
|
176
176
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
177
|
-
whitelist = self.
|
|
177
|
+
whitelist = self._duration_whitelist
|
|
178
178
|
with self._lock:
|
|
179
179
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
180
180
|
if any(find_user_info(node["current_users"], user_id) for node in nodes):
|
|
@@ -77,7 +77,8 @@ def apply_max_duration_limit(user_list, max_duration, whitelist=None):
|
|
|
77
77
|
Returns a list of user_ids whose durations were clamped.
|
|
78
78
|
Whitelist users are skipped.
|
|
79
79
|
"""
|
|
80
|
-
|
|
80
|
+
if whitelist is None:
|
|
81
|
+
whitelist = []
|
|
81
82
|
clamped_users = []
|
|
82
83
|
for user_info in user_list:
|
|
83
84
|
if user_info["user_id"] in whitelist:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|