diff options
Diffstat (limited to 'jeeves/jv_core.py')
| -rw-r--r-- | jeeves/jv_core.py | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/jeeves/jv_core.py b/jeeves/jv_core.py new file mode 100644 index 0000000..b8bba33 --- /dev/null +++ b/jeeves/jv_core.py @@ -0,0 +1,147 @@ +""" +Jeeves core helpers. Not loaded directly by Sopel (not in enable list). +All jeeves plugins import this via sys.path insertion. + +Provides: ensure_setup, save, cfg, cfg_val, chan_db, CONFIG_KEYS. +""" + +import json +import os +import threading + +from sopel.config.types import FilenameAttribute, StaticSection + +# (type, default, global_only, description) +CONFIG_KEYS = { + "dj_track_duration": (int, 120, True, "seconds per track before auto-advance"), + "dj_queue_cap": (int, 50, True, "max tracks in queue"), + "art_height": (int, 6, True, "default lines for !draw (4-12)"), + "duck_spawn_min": (int, 60, False, "min seconds between duck spawns"), + "duck_spawn_max": (int, 300, False, "max seconds between duck spawns"), + "duck_flee_time": (int, 45, False, "seconds before duck flees"), + "duck_idle_rounds": (int, 3, False, "empty rounds before auto-stop (0=off)"), + "news_interval": (int, 90, False, "minutes between news broadcasts"), + "news_count": (int, 1, False, "headlines per broadcast cycle"), + "quotes_interval": (int, 60, False, "minutes between quote auto-posts"), +} + +_SEEN_MAX = 1000 + + +class JeevesSection(StaticSection): + db_path = FilenameAttribute("db_path", relative=False, default="/var/jeeves/db.json") + duck_db_path = FilenameAttribute("duck_db_path", relative=False, default="/var/jeeves/scores.db") + recipes_path = FilenameAttribute("recipes_path", relative=False, default="/var/jeeves/recipes.json") + feeds_path = FilenameAttribute("feeds_path", relative=False, default="/var/jeeves/feeds.json") + + +def _load_feeds(path): + try: + with open(path) as f: + return json.load(f) + except Exception: + return [] + + +def _load(path, feeds_path): + feeds = _load_feeds(feeds_path) + if os.path.exists(path): + try: + with open(path) as f: + data = json.load(f) + data.setdefault("config", {}) + data.setdefault("channels", {}) + data.setdefault("quotes", []) + data.setdefault("news_feeds", feeds) + data.setdefault("news_seen", []) + return data + except Exception: + pass + data = { + "config": {}, + "channels": {}, + "quotes": [], + "news_feeds": feeds, + "news_seen": [], + } + _save_raw(path, data) + return data + + +def _save_raw(path, data): + os.makedirs(os.path.dirname(path), exist_ok=True) + seen = data.get("news_seen", []) + if len(seen) > _SEEN_MAX: + data["news_seen"] = seen[-_SEEN_MAX:] + with open(path, "w") as f: + json.dump(data, f, indent=2) + + +def save(bot): + _save_raw(bot.memory["jv_db_path"], bot.memory["jv_db"]) + + +def ensure_setup(bot): + if "jv_db" in bot.memory: + return + try: + bot.config.define_section("jeeves", JeevesSection, validate=False) + db_path = bot.config.jeeves.db_path + duck_db_path = bot.config.jeeves.duck_db_path + recipes_path = bot.config.jeeves.recipes_path + feeds_path = bot.config.jeeves.feeds_path + except Exception: + db_path = "/var/jeeves/db.json" + duck_db_path = "/var/jeeves/scores.db" + recipes_path = "/var/jeeves/recipes.json" + feeds_path = "/var/jeeves/feeds.json" + + bot.memory["jv_db_path"] = db_path + bot.memory["jv_duck_db_path"] = duck_db_path + bot.memory["jv_recipes_path"] = recipes_path + bot.memory["jv_feeds_path"] = feeds_path + bot.memory["jv_db"] = _load(db_path, feeds_path) + bot.memory["jv_lock"] = threading.Lock() + + +def cfg(bot, channel=None): + db = bot.memory.get("jv_db", {}) + global_cfg = db.get("config", {}) + if channel: + chan_cfg = (db.get("channels", {}) + .get(channel.lower(), {}) + .get("config", {})) + return {**global_cfg, **chan_cfg} + return dict(global_cfg) + + +def cfg_val(bot, key, channel=None): + merged = cfg(bot, channel) + default = CONFIG_KEYS[key][1] + return type(CONFIG_KEYS[key][0])(merged.get(key, default)) + + +def chan_db(bot, channel): + db = bot.memory["jv_db"] + db.setdefault("channels", {}) + ch = db["channels"].setdefault(channel.lower(), {}) + ch.setdefault("config", {}) + ch.setdefault("news", {"enabled": True, "last_broadcast": 0}) + ch.setdefault("quotes", {"enabled": False, "last_broadcast": 0}) + return ch + + +def is_owner(bot, trigger): + return trigger.nick == bot.settings.core.owner + + +def is_op(bot, trigger): + chan = bot.channels.get(str(trigger.sender)) + if not chan: + return False + from sopel import plugin as _p + return chan.privileges.get(trigger.nick, 0) >= _p.OP + + +def is_authorized(bot, trigger): + return is_owner(bot, trigger) or is_op(bot, trigger) |
