nv-vn / TelegraML

OCaml implementation of the Telegram Bot API
MIT License
96 stars 19 forks source link

Parse text without a command #16

Open fedeb95 opened 6 years ago

fedeb95 commented 6 years ago

Hi, I want to parse every text that's sent in the chat to my bot, how do I do that? From the examples it seems that I can only define commands or use inline mode. If I missed the right part of the documentation please point that out to me. Thanks for your work

OhadRau commented 6 years ago

Once you construct your Telegram Bot module, you can directly access the messages. It doesn't use a callback for this, so you'll have to basically create your own event loop here. For example:

module MyBot = Telegram.Api.Mk(struct
  include Telegram.BotDefaults
  let token = (* blah blah blah *)
end)

let process = function
  | Result.Success (Message (id, message_info)) ->
    (* Do stuff with the message *)
  | _ -> return ()
  | Result.Failure e ->
    if e <> "Could not get head" then (* Don't spam when there's no updates *)
      Lwt_io.printl e
    else return ()

let () =
    let rec loop () =
      MyBot.pop_update ~run_cmds:false () >>= process >>= loop in
    while true do (* Recover from errors if an exception is thrown *)
      try Lwt_main.run @@ loop ()
      with _ -> ()
    done

Basic run-down of how this works: by default, the MyBot.run function will try to interpret each message as a command. What I'm doing here is basically overriding with my own run function, which doesn't run the command but still lets you process it as a message. This parses the JSON and creates a Result with the latest update each time, so for example here the process function recognizes when it gets a Message result and would let you parse the text of the message by hand. The pop_update function is pretty much the core of this code, because what it does is read the message and then removes it from the queue. If you want to still use the default parsing in addition to your own code, you could use peek_update so that it doesn't delete the messages from the queue. You can find the actual run function in the code here: https://github.com/nv-vn/TelegraML/blob/master/src/telegramApi.ml#L2196-L2208. Let me know if you want more explanation, or if you want I can add an event for all messages to the bot that lets you process it manually. The reason I'm a bit reluctant to do so right now is because this library hasn't been updated in months and there's a lot of design decisions that the creator and I are not happy with. While I'm still sort of "maintaining" it, I've put it on the backburner because I'd rather do a total rewrite and that would take some time. That said, it is something that I'm considering doing before the end of this summer. Until that point, it's unlikely that I'll change the API unless I find someone else who would like to contribute to this library.

fedeb95 commented 6 years ago

Thanks for clarifying this! Trying your code I get an unbound constructor Message, I'm sure I'm missing some open. I'm looking forward to the decisions you'll make. To me, also looking at other libraries in other languages for writing telegram bots, this seems a bit too much of code to simply parse the user text. However maybe simply adding your snippet to the documentation would be good.

OhadRau commented 6 years ago

Oh yeah, it should be Update.Message (https://github.com/nv-vn/TelegraML/blob/master/src/telegramApi.ml#L1436).

I'll go ahead and add this to the README

To me, also looking at other libraries in other languages for writing telegram bots, this seems a bit too much of code to simply parse the user text.

Agreed, there's a lot of things that seem much more complicated in TelegraML than in other alternatives. A lot of the way the library was designed seems to be more for convenience in a few well-defined use-cases rather than as an abstraction that scales well to other cases. Since I'm (hopefully) gonna start a rewrite soon (which would fix this), are there any other spots where you think I should change the API? A lot of the complexity seems to come from the fact that this uses functors to construct the bot using some default behaviors, which works very well for command-based bots or callbacks, but doesn't work so well if we want to interpret all messages in the same place and also isn't the best for letting you interact with the finished bot. I think it works okay for basic configuration stuff, but I think it would be best to avoid using callbacks as they're currently done. Instead, you could do something like:

TelegramBot.mapUpdates (
  function
  | `Message (message_id, {text; user_id}) ->
    TelegramBot.sendMessage ~reply_to:message_id "Hello"
  | update -> TelegramBot.log(update)
)

Any thoughts on that?

fedeb95 commented 6 years ago

I think your suggestion is good, allows for a clean handling of messages. As for other parts, I've only tried basic commands and this. Another thing I was wondering about was implementing some functions to handle conversations. Something like:

let TelegramBot.conversation = First ("Hi, what can I do?", 
    Then (process_order, "Are you sure?","Sorry, I didn't understand", 
        Then (confirm, "Done!","Operation aborted.",
            End)))

The functions passed could have the message as a parameter and if the message matches certain criteria it returns true and sends a message with the first string, otherwise with the second. The bot could internally keep a mutable record or something like that with the current state. Or something along this lines to enable an easy setup of a conversational bot, something that's pretty common outside telegram (like on facebook messenger where you don't have commands at all). But this may be something a user could handle himself, and I'm not sure how much it's feasible or in the scope of the library.