lockbot 2.7.0__tar.gz → 2.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lockbot-2.7.0/python/lockbot.egg-info → lockbot-2.8.0}/PKG-INFO +1 -1
- {lockbot-2.7.0 → lockbot-2.8.0}/pyproject.toml +1 -1
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/backup/scheduler.py +33 -9
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/schemas.py +4 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/base_bot.py +7 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/device_bot.py +6 -2
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/i18n/en.py +5 -4
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/i18n/zh.py +5 -3
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/io.py +4 -2
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/node_bot.py +6 -2
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/queue_bot.py +9 -3
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/utils.py +5 -1
- {lockbot-2.7.0 → lockbot-2.8.0/python/lockbot.egg-info}/PKG-INFO +1 -1
- {lockbot-2.7.0 → lockbot-2.8.0}/LICENSE +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/MANIFEST.in +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/README.md +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/admin/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/admin/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/audit/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/audit/models.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/audit/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/audit/service.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/auth/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/auth/dependencies.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/auth/models.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/auth/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/auth/schemas.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/backup/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/backup/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/backup/service.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/encryption.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/manager.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/models.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/config.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/database.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/logs/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/main.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/rate_limit.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/settings/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/settings/models.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/backend/app/settings/router.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/bot_instance.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/config.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/device_usage_alert.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/device_usage_utils.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/entry.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/env.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/handler.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/i18n/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/message_adapter.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/msg_utils.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/platforms/__init__.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/platforms/infoflow.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/request.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot/core/scheduler.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot.egg-info/SOURCES.txt +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot.egg-info/dependency_links.txt +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot.egg-info/requires.txt +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/python/lockbot.egg-info/top_level.txt +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/setup.cfg +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/tools/create_super_admin.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/tools/gen_keys.py +0 -0
- {lockbot-2.7.0 → lockbot-2.8.0}/tools/reset_super_admin_password.py +0 -0
|
@@ -4,7 +4,7 @@ Backup scheduler — daemon thread for periodic BOS backups.
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import threading
|
|
7
|
-
from datetime import datetime, timedelta
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
@@ -25,7 +25,7 @@ class BackupScheduler:
|
|
|
25
25
|
if self._thread and self._thread.is_alive():
|
|
26
26
|
return
|
|
27
27
|
self._stop_event.clear()
|
|
28
|
-
self._started_at = datetime.now(
|
|
28
|
+
self._started_at = datetime.now()
|
|
29
29
|
self._last_error = ""
|
|
30
30
|
self._thread = threading.Thread(target=self._run, daemon=True, name="backup-scheduler")
|
|
31
31
|
self._thread.start()
|
|
@@ -56,11 +56,11 @@ class BackupScheduler:
|
|
|
56
56
|
|
|
57
57
|
def _run(self):
|
|
58
58
|
while not self._stop_event.is_set():
|
|
59
|
-
self._last_heartbeat = datetime.now(
|
|
59
|
+
self._last_heartbeat = datetime.now()
|
|
60
60
|
try:
|
|
61
61
|
seconds = self._seconds_until_next()
|
|
62
62
|
if seconds is not None:
|
|
63
|
-
self._next_run_at = datetime.now(
|
|
63
|
+
self._next_run_at = datetime.now() + timedelta(seconds=seconds)
|
|
64
64
|
else:
|
|
65
65
|
self._next_run_at = None
|
|
66
66
|
if seconds is None:
|
|
@@ -76,7 +76,9 @@ class BackupScheduler:
|
|
|
76
76
|
if triggered or self._stop_event.is_set():
|
|
77
77
|
continue # Config changed or shutting down, recalculate
|
|
78
78
|
|
|
79
|
-
# Time to backup
|
|
79
|
+
# Time to backup — skip if last successful backup was recent
|
|
80
|
+
if self._should_skip():
|
|
81
|
+
continue
|
|
80
82
|
self._do_backup()
|
|
81
83
|
except Exception as exc:
|
|
82
84
|
self._last_error = str(exc)
|
|
@@ -84,6 +86,30 @@ class BackupScheduler:
|
|
|
84
86
|
# Sleep a bit before retry
|
|
85
87
|
self._stop_event.wait(timeout=60)
|
|
86
88
|
|
|
89
|
+
def _should_skip(self) -> bool:
|
|
90
|
+
"""Skip if last successful backup was less than 20 hours ago (prevents duplicates on restart)."""
|
|
91
|
+
from lockbot.backend.app.backup.service import get_backup_config
|
|
92
|
+
from lockbot.backend.app.database import SessionLocal
|
|
93
|
+
|
|
94
|
+
db = SessionLocal()
|
|
95
|
+
try:
|
|
96
|
+
config = get_backup_config(db)
|
|
97
|
+
finally:
|
|
98
|
+
db.close()
|
|
99
|
+
|
|
100
|
+
last_time = config.get("backup_last_time", "")
|
|
101
|
+
last_status = config.get("backup_last_status", "")
|
|
102
|
+
if not last_time or last_status != "success":
|
|
103
|
+
return False
|
|
104
|
+
try:
|
|
105
|
+
last_dt = datetime.strptime(last_time, "%Y-%m-%d %H:%M:%S")
|
|
106
|
+
if (datetime.now() - last_dt).total_seconds() < 20 * 3600:
|
|
107
|
+
logger.info("Skipping backup — last success was %s (< 20h ago)", last_time)
|
|
108
|
+
return True
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass
|
|
111
|
+
return False
|
|
112
|
+
|
|
87
113
|
def _seconds_until_next(self) -> int | None:
|
|
88
114
|
"""Calculate seconds until next backup. Returns None if disabled."""
|
|
89
115
|
from lockbot.backend.app.backup.service import get_backup_config
|
|
@@ -110,12 +136,10 @@ class BackupScheduler:
|
|
|
110
136
|
except (ValueError, IndexError):
|
|
111
137
|
return None
|
|
112
138
|
|
|
113
|
-
|
|
139
|
+
# Use local time (server timezone) for scheduling
|
|
140
|
+
now = datetime.now()
|
|
114
141
|
target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
|
115
142
|
if target <= now:
|
|
116
|
-
# Next day
|
|
117
|
-
from datetime import timedelta
|
|
118
|
-
|
|
119
143
|
target += timedelta(days=1)
|
|
120
144
|
|
|
121
145
|
return int((target - now).total_seconds())
|
|
@@ -53,6 +53,10 @@ def _validate_config_overrides(v: dict | None) -> dict | None:
|
|
|
53
53
|
if not (lo <= val <= hi):
|
|
54
54
|
errors.append(f"{key} must be between {lo} and {hi}")
|
|
55
55
|
|
|
56
|
+
whitelist = v.get("DURATION_WHITELIST")
|
|
57
|
+
if whitelist is not None and (not isinstance(whitelist, list) or not all(isinstance(u, str) for u in whitelist)):
|
|
58
|
+
errors.append("DURATION_WHITELIST must be a list of strings")
|
|
59
|
+
|
|
56
60
|
max_dur = v.get("MAX_LOCK_DURATION")
|
|
57
61
|
default_dur = v.get("DEFAULT_DURATION")
|
|
58
62
|
if isinstance(max_dur, int) and isinstance(default_dur, int) and max_dur != -1 and default_dur > max_dur:
|
|
@@ -121,6 +121,13 @@ class BaseLockBot:
|
|
|
121
121
|
"""
|
|
122
122
|
return self.adapter.build_reply("\u274c" + error_msg, [user_id])
|
|
123
123
|
|
|
124
|
+
def _whitelist_hint(self):
|
|
125
|
+
"""Return whitelist hint string if BOT_OWNER is configured, else empty string."""
|
|
126
|
+
owner = self.config.get_val("BOT_OWNER")
|
|
127
|
+
if not owner:
|
|
128
|
+
return ""
|
|
129
|
+
return t("error.whitelist_hint", config=self.config, owner=owner)
|
|
130
|
+
|
|
124
131
|
# ------------------------------------------------------ _msg_with_usage
|
|
125
132
|
def _msg_with_usage(self, msg_key, *, node_key=None, sep="", **kwargs):
|
|
126
133
|
"""Return ``t(msg_key, ...) + sep + self._current_usage(node_key)``."""
|
|
@@ -177,6 +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
181
|
with self._lock:
|
|
181
182
|
timestamp = int(time.time())
|
|
182
183
|
|
|
@@ -193,7 +194,7 @@ class DeviceBot(BaseLockBot):
|
|
|
193
194
|
user_id, self._msg_with_usage("error.device_in_use_or_shared", node_key=node_key)
|
|
194
195
|
)
|
|
195
196
|
|
|
196
|
-
if max_dur > 0:
|
|
197
|
+
if max_dur > 0 and user_id not in whitelist:
|
|
197
198
|
for device in devices:
|
|
198
199
|
total_duration = duration
|
|
199
200
|
user_info = find_user_info(device["current_users"], user_id)
|
|
@@ -209,6 +210,7 @@ class DeviceBot(BaseLockBot):
|
|
|
209
210
|
"error.lock_max_duration_exceeded",
|
|
210
211
|
config=self.config,
|
|
211
212
|
max_duration=format_duration(max_dur, config=self.config),
|
|
213
|
+
whitelist_hint=self._whitelist_hint(),
|
|
212
214
|
),
|
|
213
215
|
)
|
|
214
216
|
|
|
@@ -249,6 +251,7 @@ class DeviceBot(BaseLockBot):
|
|
|
249
251
|
return error_reply
|
|
250
252
|
|
|
251
253
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
254
|
+
whitelist = self.config.get_val("DURATION_WHITELIST") or []
|
|
252
255
|
with self._lock:
|
|
253
256
|
timestamp = int(time.time())
|
|
254
257
|
for node_key, dev_ids in zip(node_key_list, dev_ids_list, strict=True):
|
|
@@ -260,7 +263,7 @@ class DeviceBot(BaseLockBot):
|
|
|
260
263
|
user_id, self._msg_with_usage("error.device_exclusive_mode", node_key=node_key)
|
|
261
264
|
)
|
|
262
265
|
|
|
263
|
-
if max_dur > 0:
|
|
266
|
+
if max_dur > 0 and user_id not in whitelist:
|
|
264
267
|
for device in devices:
|
|
265
268
|
user_info = find_user_info(device["current_users"], user_id)
|
|
266
269
|
if user_info:
|
|
@@ -276,6 +279,7 @@ class DeviceBot(BaseLockBot):
|
|
|
276
279
|
"error.slock_max_duration_exceeded",
|
|
277
280
|
config=self.config,
|
|
278
281
|
max_duration=format_duration(max_dur, config=self.config),
|
|
282
|
+
whitelist_hint=self._whitelist_hint(),
|
|
279
283
|
),
|
|
280
284
|
)
|
|
281
285
|
|
|
@@ -27,8 +27,9 @@ MESSAGES = {
|
|
|
27
27
|
"error.duration_must_be_positive": "Duration must be greater than 0\n",
|
|
28
28
|
"error.node_in_use_or_shared": "Node is in use by others or in shared mode\n\n",
|
|
29
29
|
"error.node_exclusive_mode": "Node is in exclusive mode\n\n",
|
|
30
|
-
"error.lock_max_duration_exceeded": "Note: consecutive lock cannot exceed {max_duration}\n\n",
|
|
31
|
-
"error.slock_max_duration_exceeded": "Note: consecutive slock cannot exceed {max_duration}\n\n",
|
|
30
|
+
"error.lock_max_duration_exceeded": "Note: consecutive lock cannot exceed {max_duration}\n\n{whitelist_hint}",
|
|
31
|
+
"error.slock_max_duration_exceeded": "Note: consecutive slock cannot exceed {max_duration}\n\n{whitelist_hint}",
|
|
32
|
+
"error.whitelist_hint": "To lock longer, contact @{owner} to be added to the whitelist\n\n",
|
|
32
33
|
"error.node_not_requested": "You have not requested this node\n",
|
|
33
34
|
"error.unrecognized_command": "❌ Unrecognized command: {command}",
|
|
34
35
|
"error.unknown_error": "❌ Unknown error: {command}",
|
|
@@ -139,9 +140,9 @@ MESSAGES = {
|
|
|
139
140
|
"notify.booking_expired_header": "⚠️ The following bookings have expired:\n",
|
|
140
141
|
"notify.pending_bookings_header": "🗓️ Pending bookings:\n",
|
|
141
142
|
"notify.duration_clamped": (
|
|
142
|
-
"⚠️ [Lock Duration Adjusted]\n"
|
|
143
|
+
"⚠️ [Lock Duration Adjusted]\n\n"
|
|
143
144
|
"The admin has set max lock duration to {max_duration}. "
|
|
144
|
-
"Your remaining lock time has been automatically shortened.\n"
|
|
145
|
+
"Your remaining lock time has been automatically shortened.\n\n"
|
|
145
146
|
),
|
|
146
147
|
# ── Help: news (inline) ──
|
|
147
148
|
"help.news_header": "📢 Announcement:\n",
|
|
@@ -27,8 +27,9 @@ MESSAGES = {
|
|
|
27
27
|
"error.duration_must_be_positive": "【申请资源时间应大于0】\n",
|
|
28
28
|
"error.node_in_use_or_shared": "【节点正在被他人使用或处于共享状态】\n\n",
|
|
29
29
|
"error.node_exclusive_mode": "【节点正处于独占状态】\n\n",
|
|
30
|
-
"error.lock_max_duration_exceeded": "【注意: 目前禁止连续lock超过{max_duration}】\n\n",
|
|
31
|
-
"error.slock_max_duration_exceeded": "【注意: 目前禁止连续slock超过{max_duration}】\n\n",
|
|
30
|
+
"error.lock_max_duration_exceeded": "【注意: 目前禁止连续lock超过{max_duration}】\n\n{whitelist_hint}",
|
|
31
|
+
"error.slock_max_duration_exceeded": "【注意: 目前禁止连续slock超过{max_duration}】\n\n{whitelist_hint}",
|
|
32
|
+
"error.whitelist_hint": "如需长期lock,请联系管理员 @{owner} 申请加入白名单\n\n",
|
|
32
33
|
"error.node_not_requested": "【你并未申请过该节点资源】\n",
|
|
33
34
|
"error.unrecognized_command": "❌【未识别的命令】{command}",
|
|
34
35
|
"error.unknown_error": "❌【未知错误】{command}",
|
|
@@ -133,7 +134,8 @@ MESSAGES = {
|
|
|
133
134
|
"notify.booking_expired_header": "⚠️ 以下预约已失效,请重新预约:\n",
|
|
134
135
|
"notify.pending_bookings_header": "🗓️ 目前待抢锁的预约:\n",
|
|
135
136
|
"notify.duration_clamped": (
|
|
136
|
-
"⚠️【锁定时长调整通知】\n
|
|
137
|
+
"⚠️【锁定时长调整通知】\n\n"
|
|
138
|
+
"管理员已将最大锁定时长调整为 {max_duration},您的锁定剩余时长已被自动缩短,请知悉。\n\n"
|
|
137
139
|
),
|
|
138
140
|
# ── Help: news (inline) ──
|
|
139
141
|
"help.news_header": "📢 公告:\n",
|
|
@@ -114,6 +114,7 @@ def create_or_load_device_state(config=None):
|
|
|
114
114
|
state_filename = os.path.join(_bot_dir(config), "bot_state.json")
|
|
115
115
|
cluster_configs = _cfg_get(config, "CLUSTER_CONFIGS")
|
|
116
116
|
max_duration = _cfg_get(config, "MAX_LOCK_DURATION", -1)
|
|
117
|
+
duration_whitelist = _cfg_get(config, "DURATION_WHITELIST", [])
|
|
117
118
|
|
|
118
119
|
default_state = {
|
|
119
120
|
node_key: [
|
|
@@ -150,7 +151,7 @@ def create_or_load_device_state(config=None):
|
|
|
150
151
|
device_status = old_by_id[dev_id]
|
|
151
152
|
_migrate_status_fields(device_status)
|
|
152
153
|
_migrate_user_info_fields(device_status["current_users"])
|
|
153
|
-
clamped = apply_max_duration_limit(device_status["current_users"], max_duration)
|
|
154
|
+
clamped = apply_max_duration_limit(device_status["current_users"], max_duration, duration_whitelist)
|
|
154
155
|
clamped_user_ids.update(clamped)
|
|
155
156
|
default_device.update(**device_status)
|
|
156
157
|
|
|
@@ -175,6 +176,7 @@ def create_or_load_node_state(config=None):
|
|
|
175
176
|
state_filename = os.path.join(_bot_dir(config), "bot_state.json")
|
|
176
177
|
cluster_configs = _cfg_get(config, "CLUSTER_CONFIGS")
|
|
177
178
|
max_duration = _cfg_get(config, "MAX_LOCK_DURATION", -1)
|
|
179
|
+
duration_whitelist = _cfg_get(config, "DURATION_WHITELIST", [])
|
|
178
180
|
|
|
179
181
|
default_state = {
|
|
180
182
|
shortname: {
|
|
@@ -202,7 +204,7 @@ def create_or_load_node_state(config=None):
|
|
|
202
204
|
node_status.pop("fullname", None)
|
|
203
205
|
_migrate_user_info_fields(node_status.get("current_users", []))
|
|
204
206
|
_migrate_user_info_fields(node_status.get("booking_list", []))
|
|
205
|
-
clamped = apply_max_duration_limit(node_status.get("current_users", []), max_duration)
|
|
207
|
+
clamped = apply_max_duration_limit(node_status.get("current_users", []), max_duration, duration_whitelist)
|
|
206
208
|
clamped_user_ids.update(clamped)
|
|
207
209
|
default_state[node_key].update(**node_status)
|
|
208
210
|
|
|
@@ -111,6 +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
115
|
with self._lock:
|
|
115
116
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
116
117
|
if not all(
|
|
@@ -122,7 +123,7 @@ class NodeBot(BaseLockBot):
|
|
|
122
123
|
|
|
123
124
|
timestamp = int(time.time())
|
|
124
125
|
|
|
125
|
-
if max_dur > 0:
|
|
126
|
+
if max_dur > 0 and user_id not in whitelist:
|
|
126
127
|
for node in nodes:
|
|
127
128
|
total_duration = duration
|
|
128
129
|
user_info = find_user_info(node["current_users"], user_id)
|
|
@@ -138,6 +139,7 @@ class NodeBot(BaseLockBot):
|
|
|
138
139
|
"error.lock_max_duration_exceeded",
|
|
139
140
|
config=self.config,
|
|
140
141
|
max_duration=format_duration(max_dur, config=self.config),
|
|
142
|
+
whitelist_hint=self._whitelist_hint(),
|
|
141
143
|
),
|
|
142
144
|
)
|
|
143
145
|
|
|
@@ -169,6 +171,7 @@ class NodeBot(BaseLockBot):
|
|
|
169
171
|
return error_reply
|
|
170
172
|
|
|
171
173
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
174
|
+
whitelist = self.config.get_val("DURATION_WHITELIST") or []
|
|
172
175
|
with self._lock:
|
|
173
176
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
174
177
|
if not all(node["status"] != "exclusive" for node in nodes):
|
|
@@ -176,7 +179,7 @@ class NodeBot(BaseLockBot):
|
|
|
176
179
|
|
|
177
180
|
timestamp = int(time.time())
|
|
178
181
|
|
|
179
|
-
if max_dur > 0:
|
|
182
|
+
if max_dur > 0 and user_id not in whitelist:
|
|
180
183
|
for node in nodes:
|
|
181
184
|
user_info = find_user_info(node["current_users"], user_id)
|
|
182
185
|
if user_info:
|
|
@@ -190,6 +193,7 @@ class NodeBot(BaseLockBot):
|
|
|
190
193
|
"error.slock_max_duration_exceeded",
|
|
191
194
|
config=self.config,
|
|
192
195
|
max_duration=format_duration(max_dur, config=self.config),
|
|
196
|
+
whitelist_hint=self._whitelist_hint(),
|
|
193
197
|
)
|
|
194
198
|
return self.show_error(user_id, msg)
|
|
195
199
|
|
|
@@ -38,6 +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
42
|
with self._lock:
|
|
42
43
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
43
44
|
if not all(
|
|
@@ -61,7 +62,7 @@ class QueueBot(NodeBot):
|
|
|
61
62
|
users_to_notify = set()
|
|
62
63
|
users_to_notify.add(user_id)
|
|
63
64
|
|
|
64
|
-
if max_dur > 0:
|
|
65
|
+
if max_dur > 0 and user_id not in whitelist:
|
|
65
66
|
for node in nodes:
|
|
66
67
|
booking_info = find_user_info(node["booking_list"], user_id)
|
|
67
68
|
if not command_has_duration and booking_info:
|
|
@@ -81,6 +82,7 @@ class QueueBot(NodeBot):
|
|
|
81
82
|
"error.lock_max_duration_exceeded",
|
|
82
83
|
config=self.config,
|
|
83
84
|
max_duration=format_duration(max_dur, config=self.config),
|
|
85
|
+
whitelist_hint=self._whitelist_hint(),
|
|
84
86
|
),
|
|
85
87
|
)
|
|
86
88
|
|
|
@@ -130,6 +132,7 @@ class QueueBot(NodeBot):
|
|
|
130
132
|
return error_reply
|
|
131
133
|
|
|
132
134
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
135
|
+
whitelist = self.config.get_val("DURATION_WHITELIST") or []
|
|
133
136
|
with self._lock:
|
|
134
137
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
135
138
|
if any(
|
|
@@ -140,13 +143,14 @@ class QueueBot(NodeBot):
|
|
|
140
143
|
|
|
141
144
|
timestamp = int(time.time())
|
|
142
145
|
|
|
143
|
-
if max_dur > 0 and duration > max_dur:
|
|
146
|
+
if max_dur > 0 and user_id not in whitelist and duration > max_dur:
|
|
144
147
|
return self.show_error(
|
|
145
148
|
user_id,
|
|
146
149
|
t(
|
|
147
150
|
"error.lock_max_duration_exceeded",
|
|
148
151
|
config=self.config,
|
|
149
152
|
max_duration=format_duration(max_dur, config=self.config),
|
|
153
|
+
whitelist_hint=self._whitelist_hint(),
|
|
150
154
|
),
|
|
151
155
|
)
|
|
152
156
|
|
|
@@ -170,6 +174,7 @@ class QueueBot(NodeBot):
|
|
|
170
174
|
content = t("success.take_success_by", config=self.config, user_id=user_id)
|
|
171
175
|
content += self._msg_with_usage("label.before_take")
|
|
172
176
|
max_dur = self.config.get_val("MAX_LOCK_DURATION")
|
|
177
|
+
whitelist = self.config.get_val("DURATION_WHITELIST") or []
|
|
173
178
|
with self._lock:
|
|
174
179
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
175
180
|
if any(find_user_info(node["current_users"], user_id) for node in nodes):
|
|
@@ -180,13 +185,14 @@ class QueueBot(NodeBot):
|
|
|
180
185
|
users_to_notify.add(user_id)
|
|
181
186
|
nodes = [self.state.bot_state[node_key] for node_key in node_keys]
|
|
182
187
|
|
|
183
|
-
if max_dur > 0 and remaining_duration(timestamp, duration) > max_dur:
|
|
188
|
+
if max_dur > 0 and user_id not in whitelist and remaining_duration(timestamp, duration) > max_dur:
|
|
184
189
|
return self.show_error(
|
|
185
190
|
user_id,
|
|
186
191
|
t(
|
|
187
192
|
"error.lock_max_duration_exceeded",
|
|
188
193
|
config=self.config,
|
|
189
194
|
max_duration=format_duration(max_dur, config=self.config),
|
|
195
|
+
whitelist_hint=self._whitelist_hint(),
|
|
190
196
|
),
|
|
191
197
|
)
|
|
192
198
|
|
|
@@ -71,13 +71,17 @@ def remaining_duration(start_time, duration):
|
|
|
71
71
|
return max(duration - elapsed_duration, 0)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def apply_max_duration_limit(user_list, max_duration):
|
|
74
|
+
def apply_max_duration_limit(user_list, max_duration, whitelist=None):
|
|
75
75
|
"""Clamp each user's remaining duration to max_duration in place.
|
|
76
76
|
|
|
77
77
|
Returns a list of user_ids whose durations were clamped.
|
|
78
|
+
Whitelist users are skipped.
|
|
78
79
|
"""
|
|
80
|
+
whitelist = whitelist or []
|
|
79
81
|
clamped_users = []
|
|
80
82
|
for user_info in user_list:
|
|
83
|
+
if user_info["user_id"] in whitelist:
|
|
84
|
+
continue
|
|
81
85
|
final_duration = remaining_duration(user_info["start_time"], user_info["duration"])
|
|
82
86
|
if max_duration > 0 and final_duration > max_duration:
|
|
83
87
|
user_info["duration"] = user_info["duration"] - final_duration + max_duration
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|