lockbot 2.5.1__tar.gz → 2.5.3__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 (66) hide show
  1. {lockbot-2.5.1/python/lockbot.egg-info → lockbot-2.5.3}/PKG-INFO +1 -1
  2. {lockbot-2.5.1 → lockbot-2.5.3}/pyproject.toml +1 -1
  3. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/router.py +3 -3
  4. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/schemas.py +6 -0
  5. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/base_bot.py +22 -1
  6. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/device_bot.py +38 -31
  7. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/i18n/en.py +5 -0
  8. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/i18n/zh.py +3 -0
  9. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/io.py +30 -10
  10. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/node_bot.py +37 -26
  11. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/queue_bot.py +46 -30
  12. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/utils.py +7 -1
  13. {lockbot-2.5.1 → lockbot-2.5.3/python/lockbot.egg-info}/PKG-INFO +1 -1
  14. {lockbot-2.5.1 → lockbot-2.5.3}/LICENSE +0 -0
  15. {lockbot-2.5.1 → lockbot-2.5.3}/MANIFEST.in +0 -0
  16. {lockbot-2.5.1 → lockbot-2.5.3}/README.md +0 -0
  17. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/__init__.py +0 -0
  18. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/__init__.py +0 -0
  19. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/__init__.py +0 -0
  20. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/admin/__init__.py +0 -0
  21. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/admin/router.py +0 -0
  22. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/audit/__init__.py +0 -0
  23. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/audit/models.py +0 -0
  24. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/audit/router.py +0 -0
  25. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/audit/service.py +0 -0
  26. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/auth/__init__.py +0 -0
  27. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/auth/dependencies.py +0 -0
  28. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/auth/models.py +0 -0
  29. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/auth/router.py +0 -0
  30. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/auth/schemas.py +0 -0
  31. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/__init__.py +0 -0
  32. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/encryption.py +0 -0
  33. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/manager.py +0 -0
  34. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/models.py +0 -0
  35. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
  36. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/config.py +0 -0
  37. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/database.py +0 -0
  38. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/logs/__init__.py +0 -0
  39. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/main.py +0 -0
  40. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/rate_limit.py +0 -0
  41. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/settings/__init__.py +0 -0
  42. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/settings/models.py +0 -0
  43. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/backend/app/settings/router.py +0 -0
  44. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/__init__.py +0 -0
  45. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/bot_instance.py +0 -0
  46. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/config.py +0 -0
  47. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/device_usage_alert.py +0 -0
  48. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/device_usage_utils.py +0 -0
  49. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/entry.py +0 -0
  50. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/env.py +0 -0
  51. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/handler.py +0 -0
  52. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/i18n/__init__.py +0 -0
  53. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/message_adapter.py +0 -0
  54. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/msg_utils.py +0 -0
  55. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/platforms/__init__.py +0 -0
  56. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/platforms/infoflow.py +0 -0
  57. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/request.py +0 -0
  58. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot/core/scheduler.py +0 -0
  59. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot.egg-info/SOURCES.txt +0 -0
  60. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot.egg-info/dependency_links.txt +0 -0
  61. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot.egg-info/requires.txt +0 -0
  62. {lockbot-2.5.1 → lockbot-2.5.3}/python/lockbot.egg-info/top_level.txt +0 -0
  63. {lockbot-2.5.1 → lockbot-2.5.3}/setup.cfg +0 -0
  64. {lockbot-2.5.1 → lockbot-2.5.3}/tools/create_super_admin.py +0 -0
  65. {lockbot-2.5.1 → lockbot-2.5.3}/tools/gen_keys.py +0 -0
  66. {lockbot-2.5.1 → lockbot-2.5.3}/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.5.1
3
+ Version: 2.5.3
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.5.1"
7
+ version = "2.5.3"
8
8
  description = "Cluster resource management bot for IM platforms"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -312,9 +312,6 @@ def update_bot(
312
312
  if user.role != "super_admin" and bot.user_id != user.id:
313
313
  raise HTTPException(status_code=403, detail="Cannot edit another user's bot")
314
314
 
315
- if bot.status == "running":
316
- raise HTTPException(status_code=409, detail="Cannot update a running bot. Stop it first.")
317
-
318
315
  changes = {}
319
316
  if body.name is not None:
320
317
  # Check for duplicate name (excluding current bot and deleted bots)
@@ -958,6 +955,7 @@ def get_bot_logs(
958
955
  limit: int = 50,
959
956
  level: str | None = None,
960
957
  category: str | None = None,
958
+ since: str | None = None,
961
959
  user: User = Depends(get_current_user),
962
960
  db: Session = Depends(get_db),
963
961
  ):
@@ -985,6 +983,8 @@ def get_bot_logs(
985
983
  entries = [e for e in entries if e.get("level", "").upper() == level_upper]
986
984
  if category:
987
985
  entries = [e for e in entries if e.get("category") == category]
986
+ if since:
987
+ entries = [e for e in entries if e.get("created_at", "") > since]
988
988
 
989
989
  # Sort newest first, paginate
990
990
  entries.sort(key=lambda e: e.get("created_at", ""), reverse=True)
@@ -33,6 +33,12 @@ def _validate_config_overrides(v: dict | None) -> dict | None:
33
33
  lo, hi = bounds # type: ignore[misc]
34
34
  if not (lo <= val <= hi):
35
35
  errors.append(f"{key} must be between {lo} and {hi}")
36
+
37
+ max_dur = v.get("MAX_LOCK_DURATION")
38
+ default_dur = v.get("DEFAULT_DURATION")
39
+ if isinstance(max_dur, int) and isinstance(default_dur, int) and max_dur != -1 and default_dur > max_dur:
40
+ errors.append("DEFAULT_DURATION must not exceed MAX_LOCK_DURATION")
41
+
36
42
  if errors:
37
43
  raise ValueError("; ".join(errors))
38
44
  return v
@@ -26,7 +26,12 @@ class BotState:
26
26
  _loader = None # Subclasses or instances must set this
27
27
 
28
28
  def __init__(self, config=None):
29
- self.bot_state = self._loader(config=config)
29
+ result = self._loader(config=config)
30
+ if isinstance(result, tuple):
31
+ self.bot_state, self.clamped_user_ids = result
32
+ else:
33
+ self.bot_state = result
34
+ self.clamped_user_ids = set()
30
35
 
31
36
 
32
37
  class BaseLockBot:
@@ -70,6 +75,22 @@ class BaseLockBot:
70
75
  # scheduler can recalculate its next wakeup without waiting for idle.
71
76
  self._on_state_changed: Callable[[], None] | None = None
72
77
 
78
+ self._notify_clamped_users()
79
+
80
+ def _notify_clamped_users(self):
81
+ """Send notification to users whose locks were shortened by max_duration reduction."""
82
+ clamped = getattr(self.state, "clamped_user_ids", set())
83
+ if not clamped:
84
+ return
85
+ max_dur = self.config.get_val("MAX_LOCK_DURATION")
86
+ dur_str = format_duration(max_dur, config=self.config)
87
+ msg = t("notify.duration_clamped", config=self.config, max_duration=dur_str)
88
+ reply = self.adapter.build_reply(msg, list(clamped))
89
+ try:
90
+ self.adapter.send(reply)
91
+ except Exception:
92
+ self.logger.warning("Failed to notify clamped users: %s", clamped)
93
+
73
94
  def _notify_state_changed(self) -> None:
74
95
  """Call _on_state_changed if wired up (no-op otherwise)."""
75
96
  if self._on_state_changed is not None:
@@ -193,6 +193,25 @@ class DeviceBot(BaseLockBot):
193
193
  user_id, self._msg_with_usage("error.device_in_use_or_shared", node_key=node_key)
194
194
  )
195
195
 
196
+ if max_dur > 0:
197
+ for device in devices:
198
+ total_duration = duration
199
+ user_info = find_user_info(device["current_users"], user_id)
200
+ if user_info:
201
+ total_duration += user_info["duration"]
202
+ start_time = user_info["start_time"]
203
+ else:
204
+ start_time = timestamp
205
+ if remaining_duration(start_time, total_duration) > max_dur:
206
+ return self.show_error(
207
+ user_id,
208
+ t(
209
+ "error.lock_max_duration_exceeded",
210
+ config=self.config,
211
+ max_duration=format_duration(max_dur, config=self.config),
212
+ ),
213
+ )
214
+
196
215
  for node_key, dev_ids in zip(node_key_list, dev_ids_list, strict=True):
197
216
  node_status = self.state.bot_state[node_key]
198
217
  devices = [node_status[dev_id] for dev_id in dev_ids]
@@ -207,16 +226,6 @@ class DeviceBot(BaseLockBot):
207
226
  else:
208
227
  total_duration += user_info["duration"]
209
228
 
210
- if max_dur > 0 and remaining_duration(user_info["start_time"], total_duration) > max_dur:
211
- return self.show_error(
212
- user_id,
213
- t(
214
- "error.lock_max_duration_exceeded",
215
- config=self.config,
216
- max_duration=format_duration(max_dur, config=self.config),
217
- ),
218
- )
219
-
220
229
  user_info["duration"] = total_duration
221
230
  user_info["is_notified"] = False
222
231
  device["current_users"] = [user_info]
@@ -251,6 +260,25 @@ class DeviceBot(BaseLockBot):
251
260
  user_id, self._msg_with_usage("error.device_exclusive_mode", node_key=node_key)
252
261
  )
253
262
 
263
+ if max_dur > 0:
264
+ for device in devices:
265
+ user_info = find_user_info(device["current_users"], user_id)
266
+ if user_info:
267
+ total_duration = user_info["duration"] + duration
268
+ start_time = user_info["start_time"]
269
+ else:
270
+ total_duration = duration
271
+ start_time = timestamp
272
+ if remaining_duration(start_time, total_duration) > max_dur:
273
+ return self.show_error(
274
+ user_id,
275
+ t(
276
+ "error.slock_max_duration_exceeded",
277
+ config=self.config,
278
+ max_duration=format_duration(max_dur, config=self.config),
279
+ ),
280
+ )
281
+
254
282
  for node_key, dev_ids in zip(node_key_list, dev_ids_list, strict=True):
255
283
  node_status = self.state.bot_state[node_key]
256
284
  devices = [node_status[dev_id] for dev_id in dev_ids]
@@ -260,29 +288,8 @@ class DeviceBot(BaseLockBot):
260
288
  user_info = find_user_info(device["current_users"], user_id)
261
289
  if not user_info:
262
290
  user_info = create_user_info(user_id, duration, timestamp, config=self.config)
263
- if max_dur > 0 and remaining_duration(user_info["start_time"], user_info["duration"]) > max_dur:
264
- return self.show_error(
265
- user_id,
266
- t(
267
- "error.slock_max_duration_exceeded",
268
- config=self.config,
269
- max_duration=format_duration(max_dur, config=self.config),
270
- ),
271
- )
272
291
  device["current_users"].append(user_info)
273
292
  else:
274
- if (
275
- max_dur > 0
276
- and remaining_duration(user_info["start_time"], user_info["duration"] + duration) > max_dur
277
- ):
278
- return self.show_error(
279
- user_id,
280
- t(
281
- "error.slock_max_duration_exceeded",
282
- config=self.config,
283
- max_duration=format_duration(max_dur, config=self.config),
284
- ),
285
- )
286
293
  user_info["duration"] += duration
287
294
  user_info["is_notified"] = False
288
295
 
@@ -138,6 +138,11 @@ MESSAGES = {
138
138
  "notify.resource_available_header": "📢 Resource available\nResource is idle, please lock within {timeout}:\n\n",
139
139
  "notify.booking_expired_header": "⚠️ The following bookings have expired:\n",
140
140
  "notify.pending_bookings_header": "🗓️ Pending bookings:\n",
141
+ "notify.duration_clamped": (
142
+ "⚠️ [Lock Duration Adjusted]\n"
143
+ "The admin has set max lock duration to {max_duration}. "
144
+ "Your remaining lock time has been automatically shortened.\n"
145
+ ),
141
146
  # ── Help: news (inline) ──
142
147
  "help.news_header": "📢 Announcement:\n",
143
148
  # ── Help: project links ──
@@ -132,6 +132,9 @@ MESSAGES = {
132
132
  "notify.resource_available_header": "📢【资源可用提醒】\n资源已空闲,请在 {timeout} 内lock:\n\n",
133
133
  "notify.booking_expired_header": "⚠️ 以下预约已失效,请重新预约:\n",
134
134
  "notify.pending_bookings_header": "🗓️ 目前待抢锁的预约:\n",
135
+ "notify.duration_clamped": (
136
+ "⚠️【锁定时长调整通知】\n管理员已将最大锁定时长调整为 {max_duration},您的锁定剩余时长已被自动缩短,请知悉。\n"
137
+ ),
135
138
  # ── Help: news (inline) ──
136
139
  "help.news_header": "📢 公告:\n",
137
140
  # ── Help: project links ──
@@ -106,7 +106,11 @@ def log_to_file(user_id, command, node_key, dev_ids="", duration="", config=None
106
106
 
107
107
 
108
108
  def create_or_load_device_state(config=None):
109
- """Create or load device cluster state, applying max-duration limits."""
109
+ """Create or load device cluster state, applying max-duration limits.
110
+
111
+ Returns (state_dict, clamped_user_ids) where clamped_user_ids is a set of
112
+ user IDs whose lock durations were shortened by max_duration enforcement.
113
+ """
110
114
  state_filename = os.path.join(_bot_dir(config), "bot_state.json")
111
115
  cluster_configs = _cfg_get(config, "CLUSTER_CONFIGS")
112
116
  max_duration = _cfg_get(config, "MAX_LOCK_DURATION", -1)
@@ -126,11 +130,13 @@ def create_or_load_device_state(config=None):
126
130
 
127
131
  _migrate_old_state_file(state_filename, config)
128
132
 
133
+ clamped_user_ids = set()
134
+
129
135
  if os.path.exists(state_filename):
130
136
  loaded_state = _load_state_from_file(state_filename)
131
137
 
132
138
  if not loaded_state:
133
- return default_state
139
+ return default_state, clamped_user_ids
134
140
 
135
141
  for node_key, node_status in loaded_state.items():
136
142
  if node_key not in cluster_configs:
@@ -144,20 +150,28 @@ def create_or_load_device_state(config=None):
144
150
  device_status = old_by_id[dev_id]
145
151
  _migrate_status_fields(device_status)
146
152
  _migrate_user_info_fields(device_status["current_users"])
147
- apply_max_duration_limit(device_status["current_users"], max_duration)
153
+ clamped = apply_max_duration_limit(device_status["current_users"], max_duration)
154
+ clamped_user_ids.update(clamped)
148
155
  default_device.update(**device_status)
149
156
 
150
- return default_state
157
+ if clamped_user_ids:
158
+ save_bot_state_to_file(default_state, config)
159
+
160
+ return default_state, clamped_user_ids
151
161
 
152
162
  else:
153
- return default_state
163
+ return default_state, clamped_user_ids
154
164
 
155
165
 
156
166
  # ── Node state ─────────────────────────────────────────────────
157
167
 
158
168
 
159
169
  def create_or_load_node_state(config=None):
160
- """Create or load node state, applying max-duration limits."""
170
+ """Create or load node state, applying max-duration limits.
171
+
172
+ Returns (state_dict, clamped_user_ids) where clamped_user_ids is a set of
173
+ user IDs whose lock durations were shortened by max_duration enforcement.
174
+ """
161
175
  state_filename = os.path.join(_bot_dir(config), "bot_state.json")
162
176
  cluster_configs = _cfg_get(config, "CLUSTER_CONFIGS")
163
177
  max_duration = _cfg_get(config, "MAX_LOCK_DURATION", -1)
@@ -173,11 +187,13 @@ def create_or_load_node_state(config=None):
173
187
 
174
188
  _migrate_old_state_file(state_filename, config)
175
189
 
190
+ clamped_user_ids = set()
191
+
176
192
  if os.path.exists(state_filename):
177
193
  loaded_state = _load_state_from_file(state_filename)
178
194
 
179
195
  if not loaded_state:
180
- return default_state
196
+ return default_state, clamped_user_ids
181
197
 
182
198
  for node_key, node_status in loaded_state.items():
183
199
  if node_key not in default_state:
@@ -186,13 +202,17 @@ def create_or_load_node_state(config=None):
186
202
  node_status.pop("fullname", None)
187
203
  _migrate_user_info_fields(node_status.get("current_users", []))
188
204
  _migrate_user_info_fields(node_status.get("booking_list", []))
189
- apply_max_duration_limit(node_status.get("current_users", []), max_duration)
205
+ clamped = apply_max_duration_limit(node_status.get("current_users", []), max_duration)
206
+ clamped_user_ids.update(clamped)
190
207
  default_state[node_key].update(**node_status)
191
208
 
192
- return default_state
209
+ if clamped_user_ids:
210
+ save_bot_state_to_file(default_state, config)
211
+
212
+ return default_state, clamped_user_ids
193
213
 
194
214
  else:
195
- return default_state
215
+ return default_state, clamped_user_ids
196
216
 
197
217
 
198
218
  # ── State persistence ──────────────────────────────────────────
@@ -121,6 +121,26 @@ class NodeBot(BaseLockBot):
121
121
  return self.show_error(user_id, self._msg_with_usage("error.node_in_use_or_shared"))
122
122
 
123
123
  timestamp = int(time.time())
124
+
125
+ if max_dur > 0:
126
+ for node in nodes:
127
+ total_duration = duration
128
+ user_info = find_user_info(node["current_users"], user_id)
129
+ if user_info:
130
+ total_duration += user_info["duration"]
131
+ start_time = user_info["start_time"]
132
+ else:
133
+ start_time = timestamp
134
+ if remaining_duration(start_time, total_duration) > max_dur:
135
+ return self.show_error(
136
+ user_id,
137
+ t(
138
+ "error.lock_max_duration_exceeded",
139
+ config=self.config,
140
+ max_duration=format_duration(max_dur, config=self.config),
141
+ ),
142
+ )
143
+
124
144
  for node in nodes:
125
145
  node["status"] = "exclusive"
126
146
 
@@ -131,16 +151,6 @@ class NodeBot(BaseLockBot):
131
151
  else:
132
152
  total_duration += user_info["duration"]
133
153
 
134
- if max_dur > 0 and remaining_duration(user_info["start_time"], total_duration) > max_dur:
135
- return self.show_error(
136
- user_id,
137
- t(
138
- "error.lock_max_duration_exceeded",
139
- config=self.config,
140
- max_duration=format_duration(max_dur, config=self.config),
141
- ),
142
- )
143
-
144
154
  user_info["duration"] = total_duration
145
155
  user_info["is_notified"] = False
146
156
  node["current_users"] = [user_info]
@@ -165,30 +175,31 @@ class NodeBot(BaseLockBot):
165
175
  return self.show_error(user_id, self._msg_with_usage("error.node_exclusive_mode"))
166
176
 
167
177
  timestamp = int(time.time())
168
- for node in nodes:
169
- node["status"] = "shared"
170
- user_info = find_user_info(node["current_users"], user_id)
171
- if not user_info:
172
- user_info = create_user_info(user_id, duration, timestamp, config=self.config)
173
- if max_dur > 0 and remaining_duration(user_info["start_time"], user_info["duration"]) > max_dur:
178
+
179
+ if max_dur > 0:
180
+ for node in nodes:
181
+ user_info = find_user_info(node["current_users"], user_id)
182
+ if user_info:
183
+ total_duration = user_info["duration"] + duration
184
+ start_time = user_info["start_time"]
185
+ else:
186
+ total_duration = duration
187
+ start_time = timestamp
188
+ if remaining_duration(start_time, total_duration) > max_dur:
174
189
  msg = t(
175
190
  "error.slock_max_duration_exceeded",
176
191
  config=self.config,
177
192
  max_duration=format_duration(max_dur, config=self.config),
178
193
  )
179
194
  return self.show_error(user_id, msg)
195
+
196
+ for node in nodes:
197
+ node["status"] = "shared"
198
+ user_info = find_user_info(node["current_users"], user_id)
199
+ if not user_info:
200
+ user_info = create_user_info(user_id, duration, timestamp, config=self.config)
180
201
  node["current_users"].append(user_info)
181
202
  else:
182
- if (
183
- max_dur > 0
184
- and remaining_duration(user_info["start_time"], user_info["duration"] + duration) > max_dur
185
- ):
186
- msg = t(
187
- "error.slock_max_duration_exceeded",
188
- config=self.config,
189
- max_duration=format_duration(max_dur, config=self.config),
190
- )
191
- return self.show_error(user_id, msg)
192
203
  user_info["duration"] += duration
193
204
  user_info["is_notified"] = False
194
205
 
@@ -60,6 +60,30 @@ class QueueBot(NodeBot):
60
60
  timestamp = int(time.time())
61
61
  users_to_notify = set()
62
62
  users_to_notify.add(user_id)
63
+
64
+ if max_dur > 0:
65
+ for node in nodes:
66
+ booking_info = find_user_info(node["booking_list"], user_id)
67
+ if not command_has_duration and booking_info:
68
+ check_duration = booking_info["duration"]
69
+ else:
70
+ check_duration = duration
71
+ user_info = find_user_info(node["current_users"], user_id)
72
+ if user_info:
73
+ check_duration += user_info["duration"]
74
+ start_time = user_info["start_time"]
75
+ else:
76
+ start_time = timestamp
77
+ if remaining_duration(start_time, check_duration) > max_dur:
78
+ return self.show_error(
79
+ user_id,
80
+ t(
81
+ "error.lock_max_duration_exceeded",
82
+ config=self.config,
83
+ max_duration=format_duration(max_dur, config=self.config),
84
+ ),
85
+ )
86
+
63
87
  for node in nodes:
64
88
  node["status"] = "exclusive"
65
89
  booking_info = find_user_info(node["booking_list"], user_id)
@@ -82,16 +106,6 @@ class QueueBot(NodeBot):
82
106
  for booking_user in node["booking_list"]:
83
107
  users_to_notify.add(booking_user["user_id"])
84
108
 
85
- if max_dur > 0 and remaining_duration(user_info["start_time"], total_duration) > max_dur:
86
- return self.show_error(
87
- user_id,
88
- t(
89
- "error.lock_max_duration_exceeded",
90
- config=self.config,
91
- max_duration=format_duration(max_dur, config=self.config),
92
- ),
93
- )
94
-
95
109
  user_info["duration"] = total_duration
96
110
  user_info["is_notified"] = False
97
111
  node["current_users"] = [user_info]
@@ -125,19 +139,19 @@ class QueueBot(NodeBot):
125
139
  return self.show_error(user_id, self._msg_with_usage("error.already_locked", sep="\n"))
126
140
 
127
141
  timestamp = int(time.time())
128
- for node in nodes:
129
- user_info = create_user_info(user_id, duration, timestamp, config=self.config)
130
142
 
131
- if max_dur > 0 and duration > max_dur:
132
- return self.show_error(
133
- user_id,
134
- t(
135
- "error.lock_max_duration_exceeded",
136
- config=self.config,
137
- max_duration=format_duration(max_dur, config=self.config),
138
- ),
139
- )
143
+ if max_dur > 0 and duration > max_dur:
144
+ return self.show_error(
145
+ user_id,
146
+ t(
147
+ "error.lock_max_duration_exceeded",
148
+ config=self.config,
149
+ max_duration=format_duration(max_dur, config=self.config),
150
+ ),
151
+ )
140
152
 
153
+ for node in nodes:
154
+ user_info = create_user_info(user_id, duration, timestamp, config=self.config)
141
155
  node["booking_list"].append(user_info)
142
156
 
143
157
  reply = self.adapter.build_reply(self._msg_with_usage("success.booking_added"), [user_id])
@@ -165,6 +179,17 @@ class QueueBot(NodeBot):
165
179
  users_to_notify = set()
166
180
  users_to_notify.add(user_id)
167
181
  nodes = [self.state.bot_state[node_key] for node_key in node_keys]
182
+
183
+ if max_dur > 0 and remaining_duration(timestamp, duration) > max_dur:
184
+ return self.show_error(
185
+ user_id,
186
+ t(
187
+ "error.lock_max_duration_exceeded",
188
+ config=self.config,
189
+ max_duration=format_duration(max_dur, config=self.config),
190
+ ),
191
+ )
192
+
168
193
  for node in nodes:
169
194
  node["status"] = "exclusive"
170
195
  remove_user_info(node["booking_list"], user_id)
@@ -189,15 +214,6 @@ class QueueBot(NodeBot):
189
214
  for user in node["booking_list"]:
190
215
  user["is_notified"] = False
191
216
  users_to_notify.add(user["user_id"])
192
- if max_dur > 0 and remaining_duration(user_info["start_time"], total_duration) > max_dur:
193
- return self.show_error(
194
- user_id,
195
- t(
196
- "error.lock_max_duration_exceeded",
197
- config=self.config,
198
- max_duration=format_duration(max_dur, config=self.config),
199
- ),
200
- )
201
217
 
202
218
  user_info["duration"] = total_duration
203
219
  user_info["is_notified"] = False
@@ -72,11 +72,17 @@ def remaining_duration(start_time, duration):
72
72
 
73
73
 
74
74
  def apply_max_duration_limit(user_list, max_duration):
75
- """Clamp each user's remaining duration to max_duration in place."""
75
+ """Clamp each user's remaining duration to max_duration in place.
76
+
77
+ Returns a list of user_ids whose durations were clamped.
78
+ """
79
+ clamped_users = []
76
80
  for user_info in user_list:
77
81
  final_duration = remaining_duration(user_info["start_time"], user_info["duration"])
78
82
  if max_duration > 0 and final_duration > max_duration:
79
83
  user_info["duration"] = user_info["duration"] - final_duration + max_duration
84
+ clamped_users.append(user_info["user_id"])
85
+ return clamped_users
80
86
 
81
87
 
82
88
  def format_access_mode(status: str, config=None) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.5.1
3
+ Version: 2.5.3
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