# --- abot.py --- import os import sys import logging from pathlib import Path from collections import deque from typing import Dict, Any from dotenv import load_dotenv from flask import Flask, jsonify from slackeventsapi import SlackEventAdapter import slack # -------------------------------------------------- # Environment & Config # -------------------------------------------------- load_dotenv(dotenv_path=Path(".") / ".env") from config import BOT_USER_ID, MAX_MESSAGE_LENGTH import slack_functions from slack_event_validation import validate_slack_event import message_processor import conversation_history import qdrant_functions # Vector DB (RAG) # -------------------------------------------------- # Logging # -------------------------------------------------- formatter = logging.Formatter( "%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" ) handler = logging.StreamHandler(sys.stderr) handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.handlers.clear() root_logger.addHandler(handler) root_logger.setLevel(logging.INFO) logging.getLogger("slack").setLevel(logging.WARNING) logging.info("abot.py logging initialized") # -------------------------------------------------- # Dummy Tool (safe fallback) # -------------------------------------------------- class DummyToolModule: TOOL_DEFINITION = { "name": "dummy_tool", "description": "Tool failed to load", "input_schema": {} } @staticmethod def dummy_func(**kwargs): logging.error("Dummy tool invoked", extra={"kwargs": kwargs}) return {"error": "Tool unavailable"} # -------------------------------------------------- # Tool Imports # -------------------------------------------------- try: import weather_tool import user_lookup_tool import mtscripter import imail_tool ALL_TOOLS_IMPORTED = True except Exception as e: logging.error("Tool import failure", exc_info=True) ALL_TOOLS_IMPORTED = False weather_tool = user_lookup_tool = mtscripter = imail_tool = DummyToolModule # -------------------------------------------------- # Global Tool Registry # -------------------------------------------------- GLOBAL_TOOL_REGISTRY: Dict[str, Dict[str, Any]] = { "get_weather": { "definition": getattr(weather_tool, "TOOL_DEFINITION", {}), "function": getattr(weather_tool, "get_weather", DummyToolModule.dummy_func), }, "get_user_info": { "definition": getattr(user_lookup_tool, "TOOL_DEFINITION", {}), "function": getattr(user_lookup_tool, "get_user_info", DummyToolModule.dummy_func), }, "generate_mikrotik_CPE_script": { "definition": getattr(mtscripter, "TOOL_DEFINITION", {}), "function": getattr(mtscripter, "generate_mikrotik_CPE_script", DummyToolModule.dummy_func), }, "get_imail_password": { "definition": getattr(imail_tool, "TOOL_DEFINITION", {}), "function": getattr(imail_tool, "get_imail_password", DummyToolModule.dummy_func), }, } logging.info(f"Registered tools: {list(GLOBAL_TOOL_REGISTRY.keys())}") # -------------------------------------------------- # Bot Profiles # -------------------------------------------------- import abot_channel_bot import techsupport_bot import integration_sandbox_bot import sales_bot import billing_bot import wireless_bot import abot_scripting_bot CHANNEL_BOT_MAPPING = { "C0D7LT3JA": techsupport_bot, "C08B9A6RPN1": abot_channel_bot, "C03U17ER7": integration_sandbox_bot, "C0DQ40MH8": sales_bot, "C2RGSA4GL": billing_bot, "C0DUFQ4BB": wireless_bot, "C09KNPDT481": abot_scripting_bot, } logging.info(f"Channel mappings loaded: {list(CHANNEL_BOT_MAPPING.keys())}") # -------------------------------------------------- # Flask + Slack Init # -------------------------------------------------- app = Flask(__name__) SIGNING_SECRET = os.getenv("SIGNING_SECRET") SLACK_TOKEN = os.getenv("SLACK_TOKEN") if not SIGNING_SECRET or not SLACK_TOKEN: sys.exit("Missing Slack credentials") slack_event_adapter = SlackEventAdapter(SIGNING_SECRET, "/slack/events", app) slack_client = slack.WebClient(token=SLACK_TOKEN) # -------------------------------------------------- # Deduplication # -------------------------------------------------- processed_event_ids = deque(maxlen=1000) # -------------------------------------------------- # Slack Message Handler # -------------------------------------------------- @slack_event_adapter.on("message") def handle_message(event_data): if not validate_slack_event(event_data, MAX_MESSAGE_LENGTH): return jsonify({"status": "invalid"}), 400 event = event_data.get("event", {}) event_id = event_data.get("event_id") api_app_id = event_data.get("api_app_id") dedupe_key = f"{api_app_id}-{event_id}" if dedupe_key in processed_event_ids: return jsonify({"status": "duplicate"}), 200 processed_event_ids.append(dedupe_key) channel = event.get("channel") user = event.get("user") text = event.get("text", "") ts = event.get("ts") if not all([channel, user, ts]): return jsonify({"status": "ignored"}), 200 is_bot_message = user == BOT_USER_ID subtype = event.get("subtype") # -------------------------------------------------- # Log message # -------------------------------------------------- if not is_bot_message: try: slack_functions.log_slack_message( slack_client, channel, user, text, ts, BOT_USER_ID ) except Exception: logging.warning("Failed to log message") # -------------------------------------------------- # RAG Insert (profile controlled) # -------------------------------------------------- profile = CHANNEL_BOT_MAPPING.get(channel) enable_insert = getattr(profile, "ENABLE_RAG_INSERT", False) if profile else False if enable_insert and not is_bot_message and not subtype: try: qdrant_functions.embed_and_store_slack_message( slack_client, channel, user, text, ts, BOT_USER_ID ) except Exception: logging.error("RAG insert failed", exc_info=True) # -------------------------------------------------- # File attachments # -------------------------------------------------- if "files" in event and not is_bot_message: try: slack_functions.handle_slack_attachments( slack_client, event, BOT_USER_ID ) except Exception: logging.error("Attachment handling failed") # -------------------------------------------------- # Mention routing # -------------------------------------------------- if f"<@{BOT_USER_ID}>" not in text or is_bot_message: return jsonify({"status": "no_mention"}), 200 if not profile: slack_client.chat_postMessage( channel=channel, text="I’m not configured for this channel." ) return jsonify({"status": "unmapped_channel"}), 200 logging.info( f"Routing mention to profile: {getattr(profile, 'BOT_IDENTIFIER', 'unknown')}" ) try: message_processor.process_mention( event_data=event_data, slack_client=slack_client, vector_store=qdrant_functions, bot_profile=profile, tool_registry=GLOBAL_TOOL_REGISTRY, ) return jsonify({"status": "processed"}), 200 except Exception as e: logging.error("process_mention failed", exc_info=True) slack_client.chat_postMessage( channel=channel, text="⚠️ An internal error occurred." ) return jsonify({"status": "error"}), 500 # -------------------------------------------------- # Health Endpoint # -------------------------------------------------- @app.route("/") def index(): return "Slack AI Bot Router running (Qdrant + Local LLM)" # -------------------------------------------------- # Run # -------------------------------------------------- if __name__ == "__main__": port = int(os.getenv("PORT", 5150)) logging.info(f"Starting server on port {port}") app.run(host="0.0.0.0", port=port, debug=False)