← back to dr-robert-li__slack-perplexity

Function bodies 23 total

All specs Real LLM only Function bodies
handle_app_home_opened function · python · L8-L60 (53 LOC)
handlers/home_handler.py
def handle_app_home_opened(client, event: dict, logger) -> None:
    """Publish the App Home view when a user opens the bot's Home tab."""
    user_id = event["user"]
    view = {
        "type": "home",
        "blocks": [
            {
                "type": "header",
                "text": {
                    "type": "plain_text",
                    "text": "Kahm-pew-terr",
                    "emoji": True,
                },
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        "*Your AI research assistant.*\n"
                        "Ask me anything and I'll search the web for a source-cited answer."
                    ),
                },
            },
            {"type": "divider"},
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                   
register_home_handler function · python · L63-L65 (3 LOC)
handlers/home_handler.py
def register_home_handler(app) -> None:
    """Register the app_home_opened event handler with the Bolt app."""
    app.event("app_home_opened")(handle_app_home_opened)
handle_mention function · python · L6-L29 (24 LOC)
handlers/mention_handler.py
def handle_mention(event, client) -> None:
    """Handle @mentions in channels — strip the mention tag, fetch context, then answer."""
    raw_text = event.get("text", "")
    user_text = MENTION_RE.sub("", raw_text).strip()

    if not user_text:
        return

    bot_user_id = get_bot_user_id(client)
    channel = event["channel"]

    if event.get("thread_ts"):
        messages = fetch_thread_history(client, channel, event["thread_ts"], event["ts"], bot_user_id)
    else:
        messages = fetch_channel_history(client, channel, bot_user_id)

    _handle_question(
        client,
        channel=channel,
        thread_ts=event["ts"],
        user_id=event.get("user", ""),
        user_text=user_text,
        messages=messages,
    )
register_mention_handler function · python · L32-L34 (3 LOC)
handlers/mention_handler.py
def register_mention_handler(app) -> None:
    """Register the @mention handler on the given Bolt app."""
    app.event("app_mention")(handle_mention)
ack_message function · python · L6-L19 (14 LOC)
handlers/message_handler.py
def ack_message(ack, event):
    """Acknowledge message events for DM (im) and group DM (mpim) channels.

    For mpim, also requires that the message @mentions the bot.
    """
    if event.get("bot_id"):
        return
    if event.get("subtype"):
        return
    channel_type = event.get("channel_type")
    if channel_type == "im":
        ack()
    elif channel_type == "mpim" and MENTION_RE.search(event.get("text", "")):
        ack()
handle_dm function · python · L22-L46 (25 LOC)
handlers/message_handler.py
def handle_dm(client, event: dict) -> None:
    """Lazy listener: process a DM, call Perplexity, reply with cited answer."""
    if event.get("bot_id"):
        return
    if event.get("subtype"):
        return
    if event.get("channel_type") != "im":
        return

    bot_user_id = get_bot_user_id(client)
    channel = event["channel"]

    if event.get("thread_ts"):
        messages = fetch_thread_history(client, channel, event["thread_ts"], event["ts"], bot_user_id)
    else:
        messages = fetch_channel_history(client, channel, bot_user_id)

    _handle_question(
        client,
        channel=channel,
        thread_ts=event["ts"],
        user_id=event.get("user", ""),
        user_text=event.get("text", ""),
        messages=messages,
    )
handle_mpim function · python · L49-L79 (31 LOC)
handlers/message_handler.py
def handle_mpim(client, event: dict) -> None:
    """Lazy listener: handle @mentions in group DMs (mpim), strip mention, answer."""
    if event.get("bot_id"):
        return
    if event.get("subtype"):
        return
    if event.get("channel_type") != "mpim":
        return

    raw_text = event.get("text", "")
    if not MENTION_RE.search(raw_text):
        return

    user_text = MENTION_RE.sub("", raw_text).strip()

    bot_user_id = get_bot_user_id(client)
    channel = event["channel"]

    if event.get("thread_ts"):
        messages = fetch_thread_history(client, channel, event["thread_ts"], event["ts"], bot_user_id)
    else:
        messages = fetch_channel_history(client, channel, bot_user_id)

    _handle_question(
        client,
        channel=channel,
        thread_ts=event["ts"],
        user_id=event.get("user", ""),
        user_text=user_text,
        messages=messages,
    )
Repobility · open methodology · https://repobility.com/research/
register_message_handlers function · python · L82-L88 (7 LOC)
handlers/message_handler.py
def register_message_handlers(app) -> None:
    """Register DM and group DM handlers on the given Bolt app.

    Both handle_dm and handle_mpim are registered as lazy listeners on the 'message'
    event. Each function guards on channel_type so only one fires per event.
    """
    app.event("message")(ack=ack_message, lazy=[handle_dm, handle_mpim])
get_bot_user_id function · python · L17-L22 (6 LOC)
handlers/shared.py
def get_bot_user_id(client) -> str:
    """Return the bot's Slack user ID, fetching and caching it on first call."""
    global _bot_user_id
    if _bot_user_id is None:
        _bot_user_id = client.auth_test()["user_id"]
    return _bot_user_id
update_slow_message function · python · L42-L48 (7 LOC)
handlers/shared.py
def update_slow_message(client, channel: str, loading_ts: str) -> None:
    """Called by the 60-second timer if Perplexity is still running."""
    client.chat_update(
        channel=channel,
        ts=loading_ts,
        text="Taking longer than expected, still working on it...",
    )
_handle_question function · python · L51-L125 (75 LOC)
handlers/shared.py
def _handle_question(
    client,
    channel: str,
    thread_ts: str | None,
    user_id: str,
    user_text: str,
    messages: list[dict] | None = None,
) -> None:
    """Shared logic: post loading indicator, call Perplexity, update with cited answer.

    When thread_ts is None (e.g. slash command / App Home), the loading message is posted
    as a top-level message. The loading message ts is then used as the thread anchor for
    all subsequent overflow chunk replies.

    Args:
        client: Slack WebClient.
        channel: Channel ID to post into.
        thread_ts: Thread timestamp to reply in, or None for top-level.
        user_id: Slack user ID of the asker.
        user_text: The question text (may contain <@UID> tags).
        messages: Optional prior conversation history for Perplexity context.
    """
    # Resolve any <@UID> tags in the question text to display names.
    user_text = resolve_uids(user_text, client)

    # Build loading message kwargs — omit thread_t
ack_ask function · python · L12-L18 (7 LOC)
handlers/slash_handler.py
def ack_ask(ack) -> None:
    """Acknowledge the /ask command unconditionally.

    Per Bolt's ack/lazy pattern the ack function must respond within 3 seconds.
    Empty-text handling is deferred to the lazy run_ask function.
    """
    ack()
run_ask function · python · L21-L58 (38 LOC)
handlers/slash_handler.py
def run_ask(body: dict, respond) -> None:
    """Lazy handler: validate text and call Perplexity via respond().

    Uses respond() instead of chat.postMessage so the slash command works in
    any channel — even ones the bot hasn't joined. respond() posts via the
    response_url which doesn't require channel membership.
    """
    text = (body.get("text") or "").strip()
    if not text:
        respond(
            text="Usage: /ask <your question>",
            response_type="ephemeral",
        )
        return

    user_id = body["user_id"]

    respond(text="Searching...", response_type="in_channel")

    try:
        result = query_perplexity(text)
        formatted = format_answer(result["answer"], result["citations"])

        if user_id not in greeted_users:
            greeted_users.add(user_id)
            full_text = GREETING + formatted
        else:
            full_text = formatted

        chunks = split_message(full_text)

        respond(text=chunks[0], response_typ
register_slash_handler function · python · L61-L63 (3 LOC)
handlers/slash_handler.py
def register_slash_handler(app) -> None:
    """Register /ask command with the Bolt app using ack/lazy pattern."""
    app.command("/ask")(ack=ack_ask, lazy=[run_ask])
resolve_uids function · python · L19-L46 (28 LOC)
services/context.py
def resolve_uids(text: str, client) -> str:
    """Replace all <@UID> tags in text with display names from Slack API.

    Lookups are cached in memory so the same UID is only fetched once.
    If a lookup fails for any reason, the raw tag is left unchanged.

    Args:
        text: The message text, potentially containing <@UID> tags.
        client: A Slack WebClient instance.

    Returns:
        The text with UID tags replaced by display names where available.
    """
    uids = re.findall(r"<@([A-Z0-9]+)>", text)
    for uid in uids:
        if uid not in _uid_cache:
            try:
                result = client.users_info(user=uid)
                display_name = result["user"]["profile"]["display_name"]
                real_name = result["user"]["real_name"]
                # Fall back to real_name if display_name is empty
                _uid_cache[uid] = display_name if display_name else real_name
            except Exception:
                # Leave raw tag — do not cache 
Repobility — same analyzer, your code, free for public repos · /scan/
_truncate function · python · L53-L57 (5 LOC)
services/context.py
def _truncate(text: str) -> str:
    """Truncate text to MSG_TRUNCATE_LENGTH characters, appending '...' if cut."""
    if len(text) > MSG_TRUNCATE_LENGTH:
        return text[:MSG_TRUNCATE_LENGTH] + "..."
    return text
_build_message function · python · L60-L72 (13 LOC)
services/context.py
def _build_message(text: str, bot_user_id: str, sender_id: str) -> dict:
    """Build a structured message dict compatible with Perplexity InputMessage.

    Args:
        text: The message content (already truncated if needed).
        bot_user_id: The Slack user ID of this bot.
        sender_id: The Slack user ID of the message sender.

    Returns:
        A dict with type, role, and content keys.
    """
    role = "assistant" if sender_id == bot_user_id else "user"
    return {"type": "message", "role": role, "content": _truncate(text)}
fetch_thread_history function · python · L79-L119 (41 LOC)
services/context.py
def fetch_thread_history(
    client,
    channel: str,
    thread_ts: str,
    current_ts: str,
    bot_user_id: str,
) -> list[dict]:
    """Fetch prior messages from a thread, excluding the current trigger message.

    Args:
        client: A Slack WebClient instance.
        channel: The channel ID containing the thread.
        thread_ts: The thread's root timestamp.
        current_ts: The timestamp of the message that triggered this handler
                    (excluded from results).
        bot_user_id: The Slack user ID of this bot, used for role assignment.

    Returns:
        A list of structured {type, role, content} dicts in thread order,
        up to HISTORY_DEPTH messages. Returns [] on API failure.
    """
    try:
        response = client.conversations_replies(
            channel=channel,
            ts=thread_ts,
            limit=HISTORY_DEPTH + 1,  # +1 to account for the current message
        )
        messages = [
            msg for msg in response["mess
fetch_channel_history function · python · L122-L153 (32 LOC)
services/context.py
def fetch_channel_history(
    client,
    channel: str,
    bot_user_id: str,
) -> list[dict]:
    """Fetch recent messages from a channel in chronological order.

    Args:
        client: A Slack WebClient instance.
        channel: The channel ID to fetch history from.
        bot_user_id: The Slack user ID of this bot, used for role assignment.

    Returns:
        A list of structured {type, role, content} dicts, oldest-first,
        up to HISTORY_DEPTH messages. Returns [] on API failure.
    """
    try:
        response = client.conversations_history(
            channel=channel,
            limit=HISTORY_DEPTH,
        )
        # API returns newest-first — reverse to chronological order
        messages = list(reversed(response["messages"]))

        result = []
        for msg in messages:
            text = resolve_uids(msg.get("text", ""), client)
            sender_id = msg.get("user", "")
            result.append(_build_message(text, bot_user_id, sender_id))
        
query_perplexity function · python · L11-L47 (37 LOC)
services/perplexity.py
def query_perplexity(question: str, messages: list[dict] | None = None) -> dict:
    """Query Perplexity using the pro-search preset and extract citations.

    Args:
        question: The question to ask Perplexity.
        messages: Optional list of prior conversation messages as structured
                  {type, role, content} dicts. When provided (and non-empty),
                  messages are sent as a list with the question appended as the
                  final user message. When None or empty, question is sent as a
                  plain string (backward-compatible behavior).

    Returns:
        A dict with:
            - "answer": str — the response text
            - "citations": list[dict] — up to 5 sources, each with "title" and "url"

    Raises:
        Any Perplexity SDK exceptions propagate to the caller unchanged.
    """
    if messages:
        input_items = list(messages) + [
            {"type": "message", "role": "user", "content": question}
        ]
      
markdown_to_slack function · python · L5-L64 (60 LOC)
utils/formatting.py
def markdown_to_slack(text: str) -> str:
    """Convert common Markdown patterns to Slack mrkdwn format.

    Handles: bold, italic, headings, inline code, code blocks,
    links, horizontal rules, and list markers.
    """
    # Preserve code blocks (``` ... ```) — don't transform inside them
    code_blocks: list[str] = []

    def _stash_code_block(match):
        code_blocks.append(match.group(0))
        return f"\x00CODEBLOCK{len(code_blocks) - 1}\x00"

    text = re.sub(r"```[\s\S]*?```", _stash_code_block, text)

    # Preserve inline code (` ... `)
    inline_codes: list[str] = []

    def _stash_inline_code(match):
        inline_codes.append(match.group(0))
        return f"\x00INLINE{len(inline_codes) - 1}\x00"

    text = re.sub(r"`[^`]+`", _stash_inline_code, text)

    # Headings → bold text (Slack has no heading support)
    text = re.sub(r"^#{1,6}\s+(.+)$", r"*\1*", text, flags=re.MULTILINE)

    # Bold+italic ***text*** or ___text___ → Slack bold *text*
    text = re.
format_answer function · python · L67-L84 (18 LOC)
utils/formatting.py
def format_answer(answer: str, citations: list[dict]) -> str:
    """Format a Perplexity answer with Slack mrkdwn citation footnotes.

    Converts Markdown to Slack mrkdwn and appends clickable source links.
    """
    slack_answer = markdown_to_slack(answer)

    # Safety net: cap at 5 even if caller passes more
    limited_citations = citations[:5]

    if not limited_citations:
        return f"{slack_answer}\n\n_No web sources found for this query_"

    lines = [f"{slack_answer}\n───"]
    for i, citation in enumerate(limited_citations, start=1):
        lines.append(f"[{i}] <{citation['url']}|{citation['title']}>")

    return "\n".join(lines)
split_message function · python · L87-L105 (19 LOC)
utils/formatting.py
def split_message(text: str, limit: int = 3800) -> list[str]:
    """Split a long message into chunks that fit within Slack's character limit.

    Args:
        text: The message text to split.
        limit: Maximum characters per chunk (default 3800, well under Slack's 4000).

    Returns:
        List of string chunks. Always contains at least one element.
    """
    if len(text) <= limit:
        return [text]

    chunks = []
    while text:
        chunks.append(text[:limit])
        text = text[limit:]

    return chunks