A Zulip bot agent to sit in our Black Sun. Ever evolving
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

poe: fix mentions

+35 -12
+20 -6
lib/handler.ml
··· 44 44 ) sessions [] 45 45 end 46 46 47 + (** Strip any @**name** mention from the start of content. 48 + This handles display names like @**Poe** that don't match email patterns. *) 49 + let strip_leading_mention content = 50 + let s = String.trim content in 51 + if String.length s >= 5 && String.sub s 0 3 = "@**" then 52 + match String.index_from_opt s 3 '*' with 53 + | Some i when i + 1 < String.length s && s.[i+1] = '*' -> 54 + (* Found closing **, strip the mention *) 55 + String.trim (String.sub s (i + 2) (String.length s - i - 2)) 56 + | _ -> s 57 + else s 58 + 47 59 let run_git_pull ~proc ~cwd = 48 60 Log.info (fun m -> m "Pulling latest changes from remote"); 49 61 Eio.Switch.run @@ fun sw -> ··· 159 171 let message = match scope with 160 172 | Session.Channel { stream; topic } -> 161 173 Zulip.Message.create ~type_:`Channel ~to_:[stream] ~topic ~content () 162 - | Session.Direct { user_email } -> 174 + | Session.Direct { user_email; _ } -> 163 175 Zulip.Message.create ~type_:`Direct ~to_:[user_email] ~content () 164 176 in 165 177 let _resp = Zulip.Messages.send client message in ··· 369 381 else 370 382 let session_lines = active_sessions |> List.map (fun (scope, activated_at) -> 371 383 let session = Session.load storage ~scope ~now in 372 - let scope_str = Session.scope_to_string scope in 384 + let scope_mention = Session.scope_to_mention scope in 373 385 let active_for = format_duration (now -. activated_at) in 374 386 let stats = Session.stats session in 375 - Printf.sprintf " - **%s**: %s (active for %s)" scope_str stats active_for 387 + Printf.sprintf " - %s: %s (active for %s)" scope_mention stats active_for 376 388 ) in 377 389 Printf.sprintf "- Active sessions (%d):\n%s" 378 390 (List.length active_sessions) ··· 480 492 if sender_email = bot_email then Zulip_bot.Response.silent 481 493 else 482 494 let scope = Session.scope_of_message msg in 483 - let is_mentioned = Zulip_bot.Message.is_mentioned msg ~user_email:bot_email in 495 + (* Use Zulip's "mentioned" flag rather than pattern matching on email - 496 + the flag correctly handles display name mentions like @**Poe** *) 497 + let is_mentioned = List.mem "mentioned" (Zulip_bot.Message.flags msg) in 484 498 let is_private = Zulip_bot.Message.is_private msg in 485 499 486 500 (* Check if this is a message we should respond to *) ··· 490 504 491 505 let client = Zulip_bot.Storage.client storage in 492 506 let content = 493 - Zulip_bot.Message.strip_mention msg ~user_email:bot_email 494 - |> String.trim 507 + Zulip_bot.Message.content msg 508 + |> strip_leading_mention 495 509 in 496 510 Log.info (fun m -> m "Received message (mentioned): %s" content); 497 511 match Commands.parse content with
+9 -5
lib/session.ml
··· 37 37 (** Session scope - either a channel+topic or a DM with a user *) 38 38 type scope = 39 39 | Channel of { stream : string; topic : string } 40 - | Direct of { user_email : string } 40 + | Direct of { user_email : string; user_full_name : string } 41 41 42 42 let scope_to_key = function 43 43 | Channel { stream; topic } -> 44 44 Printf.sprintf "poe:session:channel:%s:%s" stream topic 45 - | Direct { user_email } -> Printf.sprintf "poe:session:dm:%s" user_email 45 + | Direct { user_email; _ } -> Printf.sprintf "poe:session:dm:%s" user_email 46 46 47 47 let scope_to_string = function 48 48 | Channel { stream; topic } -> Printf.sprintf "channel %s > %s" stream topic 49 - | Direct { user_email } -> Printf.sprintf "DM with %s" user_email 49 + | Direct { user_full_name; _ } -> Printf.sprintf "DM with %s" user_full_name 50 + 51 + let scope_to_mention = function 52 + | Channel { stream; topic } -> Printf.sprintf "#**%s>%s**" stream topic 53 + | Direct { user_full_name; _ } -> Printf.sprintf "@**%s**" user_full_name 50 54 51 55 (** Session data stored in Zulip bot storage *) 52 56 type t = { turns : turn list; created_at : float; updated_at : float } ··· 70 74 let scope_of_message (msg : Zulip_bot.Message.t) : scope = 71 75 match msg with 72 76 | Zulip_bot.Message.Private { common; _ } -> 73 - Direct { user_email = common.sender_email } 77 + Direct { user_email = common.sender_email; user_full_name = common.sender_full_name } 74 78 | Zulip_bot.Message.Stream { common = _; display_recipient; subject; _ } -> 75 79 Channel { stream = display_recipient; topic = subject } 76 80 | Zulip_bot.Message.Unknown { common; _ } -> 77 81 (* Fall back to treating as DM *) 78 - Direct { user_email = common.sender_email } 82 + Direct { user_email = common.sender_email; user_full_name = common.sender_full_name } 79 83 80 84 (** Load session from storage *) 81 85 let load storage ~scope ~now : t =
+6 -1
lib/session.mli
··· 20 20 (** Session scope - either a channel+topic or a DM with a user *) 21 21 type scope = 22 22 | Channel of { stream : string; topic : string } 23 - | Direct of { user_email : string } 23 + | Direct of { user_email : string; user_full_name : string } 24 24 25 25 val scope_to_string : scope -> string 26 26 (** [scope_to_string scope] returns a human-readable description of the scope. *) 27 + 28 + val scope_to_mention : scope -> string 29 + (** [scope_to_mention scope] returns a Zulip-compatible linked mention. 30 + For channels, returns [#**stream>topic**] format. 31 + For DMs, returns [DM with `email`] format. *) 27 32 28 33 (** Session data *) 29 34 type t