Opslog: timestamped operational log per channel with add, list, search, and delete. SQLite-backed, admin-only clear. Note: persistent per-channel key-value store with set, get, del, list, clear. SQLite-backed, admin-only clear. Subdomain: enumeration via crt.sh CT log query with optional DNS brute force using a built-in 80-word prefix wordlist. Resolves discovered subdomains concurrently. Headers: HTTP header fingerprinting against 50+ signature patterns. Detects servers, frameworks, CDNs, and security headers (HSTS, CSP, XFO, etc). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""Plugin: timestamped operational log (SQLite per-channel)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import sqlite3
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from derp.plugin import command
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_DB_PATH = Path("data/opslog.db")
|
|
_MAX_LIST = 10
|
|
_MAX_SEARCH = 10
|
|
|
|
_conn: sqlite3.Connection | None = None
|
|
|
|
|
|
def _db() -> sqlite3.Connection:
|
|
"""Lazy-init the database connection and schema."""
|
|
global _conn
|
|
if _conn is not None:
|
|
return _conn
|
|
_DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
_conn = sqlite3.connect(str(_DB_PATH))
|
|
_conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS entries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel TEXT NOT NULL,
|
|
nick TEXT NOT NULL,
|
|
ts TEXT NOT NULL,
|
|
message TEXT NOT NULL
|
|
)
|
|
""")
|
|
_conn.execute("CREATE INDEX IF NOT EXISTS idx_entries_channel ON entries(channel)")
|
|
_conn.commit()
|
|
return _conn
|
|
|
|
|
|
@command("opslog", help="Op log: !opslog add|list|search|del|clear")
|
|
async def cmd_opslog(bot, message):
|
|
"""Timestamped operational log per channel.
|
|
|
|
Usage:
|
|
!opslog add <text> Add a log entry
|
|
!opslog list [n] Show last n entries (default 5)
|
|
!opslog search <term> Search entries
|
|
!opslog del <id> Delete an entry
|
|
!opslog clear Clear all entries for this channel (admin)
|
|
"""
|
|
parts = message.text.split(None, 2)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, "Usage: !opslog <add|list|search|del|clear> [args]")
|
|
return
|
|
|
|
sub = parts[1].lower()
|
|
rest = parts[2] if len(parts) > 2 else ""
|
|
channel = message.target or "dm"
|
|
|
|
if sub == "add":
|
|
if not rest:
|
|
await bot.reply(message, "Usage: !opslog add <text>")
|
|
return
|
|
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
db = _db()
|
|
cur = db.execute(
|
|
"INSERT INTO entries (channel, nick, ts, message) VALUES (?, ?, ?, ?)",
|
|
(channel, message.nick or "?", ts, rest),
|
|
)
|
|
db.commit()
|
|
await bot.reply(message, f"[{cur.lastrowid}] logged")
|
|
|
|
elif sub == "list":
|
|
limit = _MAX_LIST
|
|
if rest:
|
|
try:
|
|
limit = min(int(rest), _MAX_LIST)
|
|
except ValueError:
|
|
pass
|
|
db = _db()
|
|
rows = db.execute(
|
|
"SELECT id, nick, ts, message FROM entries WHERE channel = ? "
|
|
"ORDER BY id DESC LIMIT ?",
|
|
(channel, limit),
|
|
).fetchall()
|
|
if not rows:
|
|
await bot.reply(message, "No entries")
|
|
return
|
|
for row_id, nick, ts, msg in reversed(rows):
|
|
await bot.reply(message, f"[{row_id}] {ts} <{nick}> {msg}")
|
|
|
|
elif sub == "search":
|
|
if not rest:
|
|
await bot.reply(message, "Usage: !opslog search <term>")
|
|
return
|
|
db = _db()
|
|
rows = db.execute(
|
|
"SELECT id, nick, ts, message FROM entries "
|
|
"WHERE channel = ? AND message LIKE ? ORDER BY id DESC LIMIT ?",
|
|
(channel, f"%{rest}%", _MAX_SEARCH),
|
|
).fetchall()
|
|
if not rows:
|
|
await bot.reply(message, f"No entries matching '{rest}'")
|
|
return
|
|
for row_id, nick, ts, msg in reversed(rows):
|
|
await bot.reply(message, f"[{row_id}] {ts} <{nick}> {msg}")
|
|
|
|
elif sub == "del":
|
|
if not rest:
|
|
await bot.reply(message, "Usage: !opslog del <id>")
|
|
return
|
|
try:
|
|
entry_id = int(rest)
|
|
except ValueError:
|
|
await bot.reply(message, "Invalid ID")
|
|
return
|
|
db = _db()
|
|
cur = db.execute(
|
|
"DELETE FROM entries WHERE id = ? AND channel = ?",
|
|
(entry_id, channel),
|
|
)
|
|
db.commit()
|
|
if cur.rowcount:
|
|
await bot.reply(message, f"Deleted entry {entry_id}")
|
|
else:
|
|
await bot.reply(message, f"Entry {entry_id} not found")
|
|
|
|
elif sub == "clear":
|
|
if not bot._is_admin(message):
|
|
await bot.reply(message, "Permission denied: clear requires admin")
|
|
return
|
|
db = _db()
|
|
cur = db.execute("DELETE FROM entries WHERE channel = ?", (channel,))
|
|
db.commit()
|
|
await bot.reply(message, f"Cleared {cur.rowcount} entries")
|
|
|
|
else:
|
|
await bot.reply(message, "Usage: !opslog <add|list|search|del|clear> [args]")
|