forked from rmmh/skybot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbot.py
More file actions
executable file
·109 lines (79 loc) · 2.92 KB
/
bot.py
File metadata and controls
executable file
·109 lines (79 loc) · 2.92 KB
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
#!/usr/bin/env python3
from __future__ import annotations
import logging
import os
import queue
import sys
import traceback
import time
from pathlib import Path
from typing import Any, NoReturn
class Bot:
def __init__(self):
self.conns = {}
self.persist_dir = str(BASE_DIR / "persist")
Path(self.persist_dir).mkdir(parents=True, exist_ok=True)
BASE_DIR = Path(__file__).resolve().parent
bot = Bot()
# These are dynamically defined/overwritten by `core/reload.py` (and other core
# modules it loads). Stubs keep type-checkers happy and fail fast if called
# before bootstrapping.
def reload(*_args: Any, **_kwargs: Any) -> None: # type: ignore[override]
raise RuntimeError("reload() not bootstrapped yet")
def config(*_args: Any, **_kwargs: Any) -> None: # type: ignore[override]
raise RuntimeError("config() not bootstrapped yet")
def _configure_logging() -> None:
# Default to INFO; override with env if desired.
level_name = os.environ.get("SKYBOT_LOG_LEVEL", "INFO").upper()
level = getattr(logging, level_name, logging.INFO)
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
def _bootstrap_reloader() -> None:
# Keep the existing dynamic exec-based loader semantics, but do it safely.
reload_path = BASE_DIR / "core" / "reload.py"
code = reload_path.read_text(encoding="utf-8")
exec(compile(code, str(reload_path), "exec"), globals())
def run() -> NoReturn:
_configure_logging()
log = logging.getLogger("skybot")
# Do stuff relative to the install directory.
os.chdir(BASE_DIR)
# Ensure core can `import hook` and friends.
for rel in ("plugins", "lib"):
path = str(BASE_DIR / rel)
if path not in sys.path:
sys.path.insert(0, path)
log.info("Loading plugins")
# bootstrap the reloader
_bootstrap_reloader()
reload(init=True)
log.info("Connecting to IRC")
try:
config()
if not hasattr(bot, "config"):
raise RuntimeError("config() did not set bot.config")
except Exception as exc:
log.error("Malformed config file: %s", exc)
traceback.print_exc()
raise SystemExit(1)
# Core defines a message-dispatch handler named `main` (loaded via reload()).
dispatch = globals().get("main")
if not callable(dispatch):
log.error("Core did not define a callable main(conn, out)")
raise SystemExit(1)
log.info("Running main loop")
while True:
reload() # these functions only do things
config() # if changes have occurred
for conn in bot.conns.values():
try:
out = conn.out.get_nowait()
dispatch(conn, out)
except queue.Empty:
pass
while bot.conns and all(conn.out.empty() for conn in bot.conns.values()):
time.sleep(0.1)
if __name__ == "__main__":
run()