1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
"""
Sopel plugin: osterman - anti-idle
Warns idle users via NOTICE, kicks if they stay idle past the kick threshold.
Idle state is tracked per channel. Thresholds are in minutes; set to 0
to disable warn or kick.
Degrades gracefully by privilege level:
halfop or op - warn and kick
none - warn only (cannot kick)
!afk - reset your own idle timer
!idle on|off - enable/disable idle checking (ACL/owner; per channel if in channel, global if PM)
!idle <nick> - show how long a nick has been idle in this channel
"""
import json
import os
import time
from sopel import plugin
def _bot_has_halfop(bot, channel):
chan = bot.channels.get(channel)
if not chan:
return False
return chan.privileges.get(bot.nick, 0) >= plugin.HALFOP
def _chan_cfg(bot, channel):
global_cfg = bot.memory.get("os_db", {}).get("config", {})
chan_cfg = (bot.memory.get("os_db", {})
.get("channels", {})
.get(channel.lower(), {})
.get("config", {}))
return {**global_cfg, **chan_cfg}
@plugin.interval(60)
def _idle_tick(bot):
now = time.time()
last = bot.memory.get("os_last", {})
for channel_name, channel in list(bot.channels.items()):
cfg = _chan_cfg(bot, channel_name)
warn_min = cfg.get("idle_warn_min", 45)
kick_min = cfg.get("idle_kick_min", 60)
if warn_min <= 0 and kick_min <= 0:
continue
can_kick = _bot_has_halfop(bot, channel_name)
for nick in list(channel.users):
if nick == bot.nick:
continue
idle_sec = now - last.get((channel_name, nick), now)
idle_min = idle_sec / 60
if kick_min > 0 and idle_min >= kick_min:
if can_kick:
bot.kick(channel_name, nick, f"idle for {int(idle_min)}m")
last.pop((channel_name, nick), None)
else:
bot.notice(f"you have been idle for {int(idle_min)}m in {channel_name} (cannot kick without halfop)", nick)
elif warn_min > 0 and idle_min >= warn_min:
bot.notice(f"you have been idle for {int(idle_min)}m in {channel_name} - say something or get kicked", nick)
@plugin.command("afk")
@plugin.require_chanmsg
def cmd_afk(bot, trigger):
channel = str(trigger.sender)
bot.memory.setdefault("os_last", {})[(channel, trigger.nick)] = time.time()
bot.notice("idle timer reset", trigger.nick)
def _is_owner(bot, trigger):
return trigger.nick == bot.settings.core.owner
def _is_acl(bot, trigger):
return trigger.nick in bot.memory.get("os_db", {}).get("acl", [])
@plugin.command("idle", "id")
def cmd_idle(bot, trigger):
arg = (trigger.group(2) or "").strip().lower()
in_chan = str(trigger.sender).startswith("#")
if arg in ("on", "off"):
if not _is_owner(bot, trigger) and not _is_acl(bot, trigger):
return
db = bot.memory.get("os_db", {})
if in_chan:
channel = str(trigger.sender)
cfg = (db.setdefault("channels", {})
.setdefault(channel.lower(), {})
.setdefault("config", {}))
else:
cfg = db.setdefault("config", {})
if arg == "off":
cfg["idle_warn_min"] = 0
cfg["idle_kick_min"] = 0
bot.notice("idle checking disabled", trigger.nick)
else:
cfg["idle_warn_min"] = 45
cfg["idle_kick_min"] = 60
bot.notice("idle checking enabled (warn 45m, kick 60m)", trigger.nick)
path = bot.memory.get("os_db_path", "/var/osterman/db.json")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as fp:
json.dump(db, fp, indent=2)
return
target = arg or trigger.nick
if not in_chan:
bot.notice("use !idle in a channel to check idle time", trigger.nick)
return
channel = str(trigger.sender)
last = bot.memory.get("os_last", {}).get((channel, target))
if last is None:
bot.say(f"{target}: no idle data", trigger.sender)
return
diff = int(time.time() - last)
if diff < 60:
idle_str = f"{diff}s"
elif diff < 3600:
idle_str = f"{diff // 60}m"
elif diff < 86400:
idle_str = f"{diff // 3600}h {(diff % 3600) // 60}m"
else:
idle_str = f"{diff // 86400}d {(diff % 86400) // 3600}h"
bot.say(f"{target} idle: {idle_str}", trigger.sender)
|