lockbot 2.8.0__tar.gz → 2.9.1__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.1}/PKG-INFO +1 -1
  2. {lockbot-2.8.0 → lockbot-2.9.1}/pyproject.toml +1 -1
  3. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/router.py +34 -0
  4. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/schemas.py +15 -0
  5. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/base_bot.py +80 -0
  6. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/device_bot.py +9 -4
  7. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/device_usage_utils.py +3 -2
  8. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/handler.py +2 -0
  9. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/i18n/en.py +6 -0
  10. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/i18n/zh.py +6 -0
  11. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/node_bot.py +14 -8
  12. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/queue_bot.py +13 -7
  13. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/utils.py +2 -1
  14. {lockbot-2.8.0 → lockbot-2.9.1/python/lockbot.egg-info}/PKG-INFO +1 -1
  15. {lockbot-2.8.0 → lockbot-2.9.1}/LICENSE +0 -0
  16. {lockbot-2.8.0 → lockbot-2.9.1}/MANIFEST.in +0 -0
  17. {lockbot-2.8.0 → lockbot-2.9.1}/README.md +0 -0
  18. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/__init__.py +0 -0
  19. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/__init__.py +0 -0
  20. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/__init__.py +0 -0
  21. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/admin/__init__.py +0 -0
  22. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/admin/router.py +0 -0
  23. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/audit/__init__.py +0 -0
  24. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/audit/models.py +0 -0
  25. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/audit/router.py +0 -0
  26. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/audit/service.py +0 -0
  27. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/auth/__init__.py +0 -0
  28. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/auth/dependencies.py +0 -0
  29. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/auth/models.py +0 -0
  30. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/auth/router.py +0 -0
  31. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/auth/schemas.py +0 -0
  32. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/backup/__init__.py +0 -0
  33. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/backup/router.py +0 -0
  34. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/backup/scheduler.py +0 -0
  35. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/backup/service.py +0 -0
  36. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/__init__.py +0 -0
  37. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/encryption.py +0 -0
  38. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/manager.py +0 -0
  39. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/models.py +0 -0
  40. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
  41. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/config.py +0 -0
  42. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/database.py +0 -0
  43. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/logs/__init__.py +0 -0
  44. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/main.py +0 -0
  45. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/rate_limit.py +0 -0
  46. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/settings/__init__.py +0 -0
  47. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/settings/models.py +0 -0
  48. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/backend/app/settings/router.py +0 -0
  49. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/__init__.py +0 -0
  50. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/bot_instance.py +0 -0
  51. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/config.py +0 -0
  52. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/device_usage_alert.py +0 -0
  53. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/entry.py +0 -0
  54. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/env.py +0 -0
  55. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/i18n/__init__.py +0 -0
  56. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/io.py +0 -0
  57. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/message_adapter.py +0 -0
  58. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/msg_utils.py +0 -0
  59. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/platforms/__init__.py +0 -0
  60. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/platforms/infoflow.py +0 -0
  61. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/request.py +0 -0
  62. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot/core/scheduler.py +0 -0
  63. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot.egg-info/SOURCES.txt +0 -0
  64. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot.egg-info/dependency_links.txt +0 -0
  65. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot.egg-info/requires.txt +0 -0
  66. {lockbot-2.8.0 → lockbot-2.9.1}/python/lockbot.egg-info/top_level.txt +0 -0
  67. {lockbot-2.8.0 → lockbot-2.9.1}/setup.cfg +0 -0
  68. {lockbot-2.8.0 → lockbot-2.9.1}/tools/create_super_admin.py +0 -0
  69. {lockbot-2.8.0 → lockbot-2.9.1}/tools/gen_keys.py +0 -0
  70. {lockbot-2.8.0 → lockbot-2.9.1}/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.1
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.1"
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,21 @@ 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
+
71
+ allow_alias = v.get("ALLOW_USER_ALIAS")
72
+ if allow_alias is not None and not isinstance(allow_alias, bool):
73
+ errors.append("ALLOW_USER_ALIAS must be a boolean")
74
+
60
75
  max_dur = v.get("MAX_LOCK_DURATION")
61
76
  default_dur = v.get("DEFAULT_DURATION")
62
77
  if isinstance(max_dur, int) and isinstance(default_dur, int) and max_dur != -1 and default_dur > max_dur:
@@ -121,6 +121,86 @@ 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
+
136
+ def alias(self, user_id, command):
137
+ """Set or clear a node alias. Usage: alias <node_key> [alias_value]"""
138
+ if not self.config.get_val("ALLOW_USER_ALIAS"):
139
+ return self.show_error(user_id, t("alias.disabled", config=self.config))
140
+
141
+ import re
142
+
143
+ m = re.match(r"^\s*alias\s+([\w\d_-]+)(?:\s+(.{1,15}))?\s*$", command)
144
+ if not m:
145
+ return self.show_error(user_id, t("alias.usage", config=self.config))
146
+
147
+ node_key = m.group(1)
148
+ alias_val = (m.group(2) or "").strip()
149
+
150
+ cluster_configs = self.config.get_val("CLUSTER_CONFIGS")
151
+ if node_key not in cluster_configs:
152
+ return self.show_error(
153
+ user_id,
154
+ t(
155
+ "alias.invalid_node",
156
+ config=self.config,
157
+ node_key=node_key,
158
+ valid_keys=str(list(cluster_configs.keys())),
159
+ ),
160
+ )
161
+
162
+ aliases = self.config.get_val("NODE_ALIASES") or {}
163
+ if alias_val:
164
+ aliases[node_key] = alias_val
165
+ else:
166
+ aliases.pop(node_key, None)
167
+ self.config.set_val("NODE_ALIASES", aliases)
168
+
169
+ # Persist to DB if running under platform mode
170
+ self._persist_config_override("NODE_ALIASES", aliases)
171
+
172
+ if alias_val:
173
+ self.logger.info("user=%s set alias %s -> %s", user_id, node_key, alias_val)
174
+ msg = t("alias.set_ok", config=self.config, node_key=node_key, alias=alias_val)
175
+ else:
176
+ self.logger.info("user=%s cleared alias %s", user_id, node_key)
177
+ msg = t("alias.clear_ok", config=self.config, node_key=node_key)
178
+ return self.adapter.build_reply(msg, [user_id])
179
+
180
+ def _persist_config_override(self, key, value):
181
+ """Persist a single config_override key to DB (best-effort)."""
182
+ try:
183
+ import json
184
+
185
+ from lockbot.backend.app.bots.models import Bot
186
+ from lockbot.backend.app.database import SessionLocal
187
+
188
+ bot_id = self.config.get_val("BOT_ID")
189
+ if not bot_id:
190
+ return
191
+ db = SessionLocal()
192
+ try:
193
+ bot = db.query(Bot).filter(Bot.id == int(bot_id)).first()
194
+ if bot:
195
+ overrides = json.loads(bot.config_overrides) if bot.config_overrides else {}
196
+ overrides[key] = value
197
+ bot.config_overrides = json.dumps(overrides, ensure_ascii=False)
198
+ db.commit()
199
+ finally:
200
+ db.close()
201
+ except Exception:
202
+ pass
203
+
124
204
  def _whitelist_hint(self):
125
205
  """Return whitelist hint string if BOT_OWNER is configured, else empty string."""
126
206
  owner = self.config.get_val("BOT_OWNER")
@@ -39,7 +39,7 @@ class DeviceBot(BaseLockBot):
39
39
  _loader = staticmethod(create_or_load_device_state)
40
40
 
41
41
  def supported_commands(self):
42
- return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query"]
42
+ return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query", "alias"]
43
43
 
44
44
  def parse_command(self, user_id, command_key, command, parsing_duration=False):
45
45
  """
@@ -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):
@@ -493,6 +493,9 @@ class DeviceBot(BaseLockBot):
493
493
  text += t("help.query_node_example", config=self.config, node=example_node)
494
494
  text += "\n"
495
495
 
496
+ if self.config.get_val("ALLOW_USER_ALIAS"):
497
+ text += f"6. 备注: alias {example_node} <备注>\n\n"
498
+
496
499
  return text
497
500
 
498
501
  def _check_and_notify(self) -> float | None:
@@ -597,4 +600,6 @@ class DeviceBot(BaseLockBot):
597
600
  """
598
601
  Get a text description of the current cluster device usage (supports merged display and heterogeneous hints).
599
602
  """
600
- return get_current_usage(node_filter, self.state.bot_state, {}, config=self.config)
603
+ return get_current_usage(
604
+ node_filter, self.state.bot_state, {}, config=self.config, display_node=self._display_node
605
+ )
@@ -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()
@@ -55,6 +55,8 @@ def execute_command(msg_data, bot):
55
55
  return bot.query(user_id)
56
56
  elif cmd in ("help", "h"):
57
57
  return bot.print_help(user_id)
58
+ elif cmd == "alias":
59
+ return bot.alias(user_id, rcv_info)
58
60
  else:
59
61
  return bot.print_help(user_id, t("error.unknown_error", config=getattr(bot, "config", None), command=rcv_info))
60
62
  return bot.print_help(user_id, t("error.unknown_error", config=getattr(bot, "config", None), command=rcv_info))
@@ -173,6 +173,12 @@ MESSAGES = {
173
173
  "opkick.invalid_format": "Invalid opkick format. Usage: opkick <username> [node] [dev x,y]",
174
174
  "opkick.notify_kicked": "⚠️ [Operator Release]\n\nNode: {node_key}\n",
175
175
  "opkick.notify_kicked_reason": "Reason: {reason}\n\n",
176
+ # ── Alias command ──
177
+ "alias.usage": "Usage: alias <node_key> [label]\nExample: alias node0 10.10.10.56\nClear: alias node0",
178
+ "alias.invalid_node": "Node {node_key} does not exist. Available: {valid_keys}\n",
179
+ "alias.set_ok": "✅ Set {node_key} alias to「{alias}」\n\n",
180
+ "alias.clear_ok": "✅ Cleared alias for {node_key}\n\n",
181
+ "alias.disabled": "User alias command is not enabled for this bot",
176
182
  # ── Webhook: bot not running ──
177
183
  "webhook.bot_not_running": "⚠️ Bot {bot_name}(ID:{bot_id}) is not running. " # noqa: E501
178
184
  "Please contact the owner @{owner_username} to start it.",
@@ -166,6 +166,12 @@ MESSAGES = {
166
166
  "opkick.invalid_format": "opkick命令格式错误,正确格式: opkick <username> [node] [dev x,y]",
167
167
  "opkick.notify_kicked": "⚠️【运维释放】\n\n节点: {node_key}\n",
168
168
  "opkick.notify_kicked_reason": "原因: {reason}\n\n",
169
+ # ── Alias command ──
170
+ "alias.usage": "用法: alias <节点名> [备注]\n示例: alias node0 10.10.10.56\n清除: alias node0",
171
+ "alias.invalid_node": "节点 {node_key} 不存在,可用节点: {valid_keys}\n",
172
+ "alias.set_ok": "✅ 已设置 {node_key} 备注为「{alias}」\n\n",
173
+ "alias.clear_ok": "✅ 已清除 {node_key} 的备注\n\n",
174
+ "alias.disabled": "该机器人未开启用户自助设置备注功能",
169
175
  # ── Webhook: bot not running ──
170
176
  "webhook.bot_not_running": (
171
177
  "⚠️ 机器人 {bot_name}(ID:{bot_id}) 尚未启动,请联系管理人 @{owner_username} 启动后再使用。"
@@ -37,7 +37,7 @@ class NodeBot(BaseLockBot):
37
37
  _loader = staticmethod(create_or_load_node_state)
38
38
 
39
39
  def supported_commands(self):
40
- return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query"]
40
+ return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query", "alias"]
41
41
 
42
42
  def parse_command(self, user_id, command_key, command, parsing_duration=False):
43
43
  """
@@ -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):
@@ -358,7 +358,12 @@ class NodeBot(BaseLockBot):
358
358
  reply_info += t("help.section4_title", config=self.config)
359
359
  reply_info += t("help.section5_title", config=self.config)
360
360
  reply_info += t("help.query_at_bot", config=self.config)
361
- reply_info += f" {example_node0}\n\n"
361
+ reply_info += f" {example_node0}\n"
362
+
363
+ if self.config.get_val("ALLOW_USER_ALIAS"):
364
+ reply_info += f"6. 备注: alias {example_node0} <备注>\n"
365
+
366
+ reply_info += "\n"
362
367
  return reply_info
363
368
 
364
369
  def _check_and_notify(self) -> float | None:
@@ -397,7 +402,7 @@ class NodeBot(BaseLockBot):
397
402
 
398
403
  uid = user_info["user_id"] + format_access_mode(node["status"], config=self.config)
399
404
  duration = format_duration(remaining_time, config=self.config)
400
- alert_info += f"{node_key} {uid} {duration}\n"
405
+ alert_info += f"{self._display_node(node_key)} {uid} {duration}\n"
401
406
 
402
407
  if EARLY_NOTIFY and not user_info["is_notified"] and 0 < remaining_time <= TIME_ALERT:
403
408
  trigger_time_alert = True
@@ -407,7 +412,7 @@ class NodeBot(BaseLockBot):
407
412
 
408
413
  uid = user_info["user_id"] + format_access_mode(node["status"], config=self.config)
409
414
  duration = format_duration(remaining_time, config=self.config)
410
- alert_info += f"{node_key} {uid} {duration}\n"
415
+ alert_info += f"{self._display_node(node_key)} {uid} {duration}\n"
411
416
 
412
417
  for user_id in removed_users_id:
413
418
  remove_user_info(node["current_users"], user_id)
@@ -453,11 +458,12 @@ class NodeBot(BaseLockBot):
453
458
  usage_info = ""
454
459
  for node_key, node_status in nodes.items():
455
460
  if node_filter is None or node_key == node_filter:
461
+ display_name = self._display_node(node_key)
456
462
  if node_status["status"] == "idle":
457
- usage_info += "{:} {}\n".format(node_key, t("status.idle", config=self.config))
463
+ usage_info += "{:} {}\n".format(display_name, t("status.idle", config=self.config))
458
464
  else:
459
465
  for user_idx, user_info in enumerate(node_status["current_users"]):
460
- node_name = f"{node_key}" if user_idx == 0 else ""
466
+ node_name = f"{display_name}" if user_idx == 0 else ""
461
467
  uid = user_info["user_id"] + format_access_mode(node_status["status"], config=self.config)
462
468
  duration = format_duration(
463
469
  remaining_duration(user_info["start_time"], user_info["duration"]), config=self.config
@@ -26,7 +26,7 @@ class QueueBot(NodeBot):
26
26
  """
27
27
 
28
28
  def supported_commands(self):
29
- return ["lock", "unlock", "free", "kickout", "kicklock", "help", "h", "book", "take", "query"]
29
+ return ["lock", "unlock", "free", "kickout", "kicklock", "help", "h", "book", "take", "query", "alias"]
30
30
 
31
31
  def lock(self, user_id, command):
32
32
  """
@@ -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):
@@ -345,7 +345,12 @@ class QueueBot(NodeBot):
345
345
  parts.append(t("help.section_help_title_queue", config=self.config))
346
346
  parts.append(t("help.section_query_title_queue", config=self.config))
347
347
  parts.append(t("help.query_at_bot", config=self.config))
348
- parts.append(f" {example_node0}\n\n")
348
+ parts.append(f" {example_node0}\n")
349
+
350
+ if self.config.get_val("ALLOW_USER_ALIAS"):
351
+ parts.append(f"8. 备注: alias {example_node0} <备注>\n")
352
+
353
+ parts.append("\n")
349
354
  return "".join(parts)
350
355
 
351
356
  def _check_and_notify(self) -> float | None:
@@ -522,11 +527,12 @@ class QueueBot(NodeBot):
522
527
  usage_info = ""
523
528
  for node_key, node_status in nodes.items():
524
529
  if node_filter is None or node_key == node_filter:
530
+ display_name = self._display_node(node_key)
525
531
  if node_status["status"] == "idle":
526
- usage_info += "{:} {}\n".format(node_key, t("status.idle", config=self.config))
532
+ usage_info += "{:} {}\n".format(display_name, t("status.idle", config=self.config))
527
533
  else:
528
534
  for user_idx, user_info in enumerate(node_status["current_users"]):
529
- node_name = f"{node_key}" if user_idx == 0 else ""
535
+ node_name = f"{display_name}" if user_idx == 0 else ""
530
536
  uid = user_info["user_id"]
531
537
  duration = format_duration(
532
538
  remaining_duration(user_info["start_time"], user_info["duration"]),
@@ -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.1
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