aboutsummaryrefslogtreecommitdiffstats
path: root/alfred/bots.py
blob: de0a1d44cb48db1b9f2da0f2d0b6d1479a226147 (plain) (blame)
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
"""
Sopel plugin: bot controller

Owner-only commands to start, stop, and restart IRC bots directly as
processes. Alfred manages them; no rc-service or sudo required.

Bots are discovered automatically from ~/.config/sopel/*.cfg - add a new
config file there and it will appear in !bot list on the next command.

  !bot list                            - show status of all managed bots
  !bot all <start|stop|restart>        - control all bots at once
  !bot <name> <start|stop|restart>     - control a bot
  !bot <name> status                   - show status of one bot
"""

import glob
import os
import subprocess
import time

from sopel import plugin
import os as _os, sys as _sys
_alfred_dir = _os.path.dirname(_os.path.abspath(__file__))
if _alfred_dir not in _sys.path:
    _sys.path.insert(0, _alfred_dir)
import helpstrings as h


SOPEL      = "/usr/bin/sopel"
CFG_DIR    = "/home/ahmed/.config/sopel"
EXCLUDE    = {"alfred"}


def _discover():
    paths = glob.glob(os.path.join(CFG_DIR, "*.cfg"))
    return {
        os.path.splitext(os.path.basename(p))[0]: p
        for p in sorted(paths)
        if os.path.splitext(os.path.basename(p))[0] not in EXCLUDE
    }


def _is_owner(bot, trigger):
    return trigger.nick == bot.settings.core.owner


def _running(cfg_path):
    r = subprocess.run(["pgrep", "-f", f"sopel.*{cfg_path}"], capture_output=True)
    return r.returncode == 0


def _start(name, cfg_path):
    if _running(cfg_path):
        return f"{name}: already running"
    subprocess.Popen(
        [SOPEL, "--config", cfg_path],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        close_fds=True,
    )
    return f"{name}: started"


def _stop(name, cfg_path):
    r = subprocess.run(["pkill", "-f", f"sopel.*{cfg_path}"], capture_output=True)
    return f"{name}: stopped" if r.returncode == 0 else f"{name}: not running"


def _restart(name, cfg_path):
    _stop(name, cfg_path)
    for _ in range(10):
        time.sleep(0.2)
        if not _running(cfg_path):
            break
    return _start(name, cfg_path)


@plugin.command("bot")
def cmd_bot(bot, trigger):
    if not _is_owner(bot, trigger):
        return

    bots = _discover()
    args = (trigger.group(2) or "").split()

    if not args:
        bot.say(h.BOT_TOPIC)
        return

    if args[0] == "list":
        if not bots:
            bot.say("no bots found")
            return
        bot.say(" | ".join(
            f"{n}:{'up' if _running(p) else 'down'}" for n, p in bots.items()
        ))
        return

    if args[0] == "all":
        if len(args) < 2 or args[1] not in ("start", "stop", "restart"):
            bot.say("usage: !bot all <start|stop|restart>")
            return
        action = args[1]
        if not bots:
            bot.say("no bots found")
            return
        fn = {"start": _start, "stop": _stop, "restart": _restart}[action]
        results = [fn(n, p) for n, p in bots.items()]
        bot.say(" | ".join(results))
        return

    name = args[0]
    if name not in bots:
        bot.say(f"unknown bot - known: {', '.join(bots)}")
        return

    if len(args) < 2:
        bot.say(f"usage: !bot {name} <start|stop|restart|status>")
        return

    cfg_path = bots[name]
    action   = args[1]

    if action == "start":
        bot.say(_start(name, cfg_path))
    elif action == "stop":
        bot.say(_stop(name, cfg_path))
    elif action == "restart":
        bot.say(_restart(name, cfg_path))
    elif action == "status":
        bot.say(f"{name}: {'running' if _running(cfg_path) else 'stopped'}")
    else:
        bot.say(f"usage: !bot {name} <start|stop|restart|status>")