lockbot 2.5.2__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.
- {lockbot-2.5.2/python/lockbot.egg-info → lockbot-2.5.3}/PKG-INFO +1 -1
- {lockbot-2.5.2 → lockbot-2.5.3}/pyproject.toml +1 -1
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/router.py +3 -3
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/schemas.py +6 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/base_bot.py +22 -1
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/device_bot.py +38 -31
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/i18n/en.py +5 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/i18n/zh.py +3 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/io.py +30 -10
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/node_bot.py +37 -26
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/queue_bot.py +46 -30
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/utils.py +7 -1
- {lockbot-2.5.2 → lockbot-2.5.3/python/lockbot.egg-info}/PKG-INFO +1 -1
- {lockbot-2.5.2 → lockbot-2.5.3}/LICENSE +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/MANIFEST.in +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/README.md +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/admin/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/admin/router.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/audit/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/audit/models.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/audit/router.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/audit/service.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/auth/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/auth/dependencies.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/auth/models.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/auth/router.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/auth/schemas.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/encryption.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/manager.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/models.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/config.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/database.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/logs/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/main.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/rate_limit.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/settings/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/settings/models.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/backend/app/settings/router.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/bot_instance.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/config.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/device_usage_alert.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/device_usage_utils.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/entry.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/env.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/handler.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/i18n/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/message_adapter.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/msg_utils.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/platforms/__init__.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/platforms/infoflow.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/request.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot/core/scheduler.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot.egg-info/SOURCES.txt +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot.egg-info/dependency_links.txt +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot.egg-info/requires.txt +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/python/lockbot.egg-info/top_level.txt +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/setup.cfg +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/tools/create_super_admin.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/tools/gen_keys.py +0 -0
- {lockbot-2.5.2 → lockbot-2.5.3}/tools/reset_super_admin_password.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
user_info
|
|
173
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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:
|
|
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
|