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.
Files changed (70) hide show
  1. {lockbot-2.8.0/python/lockbot.egg-info → lockbot-2.9.0}/PKG-INFO +1 -1
  2. {lockbot-2.8.0 → lockbot-2.9.0}/pyproject.toml +1 -1
  3. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/router.py +34 -0
  4. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/schemas.py +11 -0
  5. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/base_bot.py +12 -0
  6. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_bot.py +5 -3
  7. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_usage_utils.py +3 -2
  8. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/node_bot.py +7 -6
  9. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/queue_bot.py +3 -3
  10. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/utils.py +2 -1
  11. {lockbot-2.8.0 → lockbot-2.9.0/python/lockbot.egg-info}/PKG-INFO +1 -1
  12. {lockbot-2.8.0 → lockbot-2.9.0}/LICENSE +0 -0
  13. {lockbot-2.8.0 → lockbot-2.9.0}/MANIFEST.in +0 -0
  14. {lockbot-2.8.0 → lockbot-2.9.0}/README.md +0 -0
  15. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/__init__.py +0 -0
  16. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/__init__.py +0 -0
  17. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/__init__.py +0 -0
  18. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/admin/__init__.py +0 -0
  19. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/admin/router.py +0 -0
  20. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/__init__.py +0 -0
  21. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/models.py +0 -0
  22. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/router.py +0 -0
  23. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/audit/service.py +0 -0
  24. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/__init__.py +0 -0
  25. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/dependencies.py +0 -0
  26. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/models.py +0 -0
  27. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/router.py +0 -0
  28. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/auth/schemas.py +0 -0
  29. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/__init__.py +0 -0
  30. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/router.py +0 -0
  31. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/scheduler.py +0 -0
  32. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/backup/service.py +0 -0
  33. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/__init__.py +0 -0
  34. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/encryption.py +0 -0
  35. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/manager.py +0 -0
  36. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/models.py +0 -0
  37. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
  38. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/config.py +0 -0
  39. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/database.py +0 -0
  40. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/logs/__init__.py +0 -0
  41. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/main.py +0 -0
  42. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/rate_limit.py +0 -0
  43. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/__init__.py +0 -0
  44. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/models.py +0 -0
  45. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/backend/app/settings/router.py +0 -0
  46. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/__init__.py +0 -0
  47. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/bot_instance.py +0 -0
  48. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/config.py +0 -0
  49. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/device_usage_alert.py +0 -0
  50. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/entry.py +0 -0
  51. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/env.py +0 -0
  52. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/handler.py +0 -0
  53. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/__init__.py +0 -0
  54. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/en.py +0 -0
  55. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/i18n/zh.py +0 -0
  56. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/io.py +0 -0
  57. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/message_adapter.py +0 -0
  58. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/msg_utils.py +0 -0
  59. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/platforms/__init__.py +0 -0
  60. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/platforms/infoflow.py +0 -0
  61. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/request.py +0 -0
  62. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot/core/scheduler.py +0 -0
  63. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/SOURCES.txt +0 -0
  64. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/dependency_links.txt +0 -0
  65. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/requires.txt +0 -0
  66. {lockbot-2.8.0 → lockbot-2.9.0}/python/lockbot.egg-info/top_level.txt +0 -0
  67. {lockbot-2.8.0 → lockbot-2.9.0}/setup.cfg +0 -0
  68. {lockbot-2.8.0 → lockbot-2.9.0}/tools/create_super_admin.py +0 -0
  69. {lockbot-2.8.0 → lockbot-2.9.0}/tools/gen_keys.py +0 -0
  70. {lockbot-2.8.0 → lockbot-2.9.0}/tools/reset_super_admin_password.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.8.0
3
+ Version: 2.9.0
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lockbot"
7
- version = "2.8.0"
7
+ version = "2.9.0"
8
8
  description = "Cluster resource management bot for IM platforms"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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.config.get_val("DURATION_WHITELIST") or []
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.config.get_val("DURATION_WHITELIST") or []
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(node_filter, self.state.bot_state, {}, config=self.config)
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
- usage_info += t("device_usage.node_header", config=config, node_key=node_key)
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.config.get_val("DURATION_WHITELIST") or []
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.config.get_val("DURATION_WHITELIST") or []
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(node_key, t("status.idle", config=self.config))
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"{node_key}" if user_idx == 0 else ""
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.config.get_val("DURATION_WHITELIST") or []
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.config.get_val("DURATION_WHITELIST") or []
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.config.get_val("DURATION_WHITELIST") or []
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
- whitelist = whitelist or []
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.8.0
3
+ Version: 2.9.0
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes