hedwig-im / hedwig

An Adapter-based Bot Framework for Elixir Applications
MIT License
656 stars 73 forks source link

the robot does not seem to react to messages sent by himself #26

Closed docteurklein closed 8 years ago

docteurklein commented 8 years ago

I'm not exactly sure why, but I'd like this to implement the !! operator.

> gif me party hard
> http://giphy.net/funny-gif
> @alfred !!
> gif me party hard
> http://giphy.net/another-funny-gif

Currently, the bot sends the message, it is displayed, and the logs show it has arrived. But the bot does not react. It seems to ignore himself.

Any ideas?

scrogson commented 8 years ago

I'm not sure I understand what you're trying to do. Wouldn't that cause a loop?

docteurklein commented 8 years ago

it wouldn't. I really only want the bot to re-interpret the last thing I messaged him. So if I message him "!!", he should execute any responder that matches the last message I sent him.

There is an implementation of that in hubot, I just can't find the sources yet.

jwarlander commented 8 years ago

Shouldn't that just be a case of saving history in a separate process? Then '!!' could just query the latest history entry, and '!43' could re-apply entry #43, etc. It's probably better to feed it back to the bot internally, than take the indirect route over the chat room.

If the bot were to trigger on its own messages, it would indeed be very easy to cause a loop..

docteurklein commented 8 years ago

@jwarlander Agreed, and that's how I tried to implement it.

I have implemented a brain, (that is a simple KV store, named process). I have a responder that hears any command (and store the last thing he hears in the brain). I have another responder that listens to "!!" and answers with the last heard thing.

Strangely, it does not react to the last message (sent by himself)

docteurklein commented 8 years ago

I still didn't get it to work, but I'll post my attempts here.

Instead of making a round-trip and let the bot hear what he just said, I tried to directly ask him to interpret what was stored:

defmodule Edgar.Responder.NotNot do
  use Hedwig.Responder

  respond ~r/(.*)/i, msg do
    case msg.matches[1] do
      "!!" -> ""
      _ -> Edgar.Brain.memorize(:lastmsg, msg.matches[1])
    end
  end

  respond ~r/!!/i, msg do
    remembered_msg = %{ msg | text: Edgar.Brain.remember(:lastmsg) }
    pid = Hedwig.whereis("edgar")
    send msg, Edgar.Robot.send(pid, remembered_msg)
  end
end

getting an error:

16:58:20.481 [error] Task #PID<0.306.0> started from #PID<0.264.0> terminating
** (ArgumentError) argument error
    :erlang.send(:undefined, {:"$gen_cast", {:send, %Hedwig.Message{adapter: {Hedwig.Adapters.Console, #PID<0.265.0>}, matches: %{0 => "@edgar !!"}, private: %{}, ref: #Reference<0.0.3.3237>, robot: %Hedwig.Robot{adapter: #PID<0.265.0>, aka: "@edgar", name: "Edgar Bot", opts: [robot: Edgar.Robot, otp_app: :edgar, responders: [{Hedwig.Responders.Help, []}, {Hedwig.Responders.Panzy, []}, {Hedwig.Responders.GreatSuccess, []}, {Hedwig.Responders.ShipIt, []}, {Edgar.Responder.Giphy, []}, {Edgar.Responder.NotNot, []}, {Edgar.Responder.Weather, []}, {Edgar.Responder.ItIsSomething, []}, {Edgar.Responder.OhYou, []}, {Edgar.Responder.TIL, []}, {Edgar.Responder.Bookmark, []}], adapter: Hedwig.Adapters.Console], responders: []}, room: nil, text: "it's something", type: "chat", user: "florian"}}})
    (elixir) lib/gen_server.ex:639: GenServer.do_send/2
    (edgar) lib/edgar/responder/NotNot.ex:26: Edgar.Responder.NotNot."__!!__"/2
    (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:40: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: #Function<2.28577963/0 in Hedwig.Responder.run_aysnc/2>
    Args: []

Any idea how I should implement that?

scrogson commented 8 years ago

@docteurklein what about calling Hedwig.Robot.handle_message/2 ?

https://github.com/hedwig-im/hedwig/blob/master/lib/hedwig/robot.ex#L183-L185

scrogson commented 8 years ago

And it looks like in your case that Hedwig.whereis("edgar") is returning :undefined. So it's not registered.

You should probably store the whole message in your brain process, instead of just the matches. Then you can run the whole message through Hedwig.Robot.handle_message(robot, msg)

scrogson commented 8 years ago

Looks like the Console adapter doesn't register the bot...so that's why you're getting :undefined

docteurklein commented 8 years ago

Thanks! I will try your propositions.

docteurklein commented 8 years ago

Hedwig.Robot.handle_message(robot, msg) is casting, so I just get :ok as a return value. How could I get what is sent back by the responder that matches msg ?

docteurklein commented 8 years ago

mmh, now I get a working test with this code, but not with the console adapter:

code

  respond ~r/!!/i, msg do
    pid = Hedwig.whereis("Edgar Bot")
    remembered = Edgar.Brain.remember(:lastmsg)
    send msg, Hedwig.Robot.handle_message(pid, %{
      remembered | robot: %{
        remembered.robot | adapter: msg.robot.adapter
      }
    })
  end

test (green)

  @tag start_robot: true, name: "edgar", responders: [{Edgar.Responder.NotNot, []}, {Hedwig.Responders.Help, []}]
  test "!! repeats last command", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "edgar help"}}
    send adapter, {:message, %{msg | text: "edgar !!"}}
    assert_receive {:message, %{text: text}}
    assert String.contains?(text, "help")
  end

console adapter error:

00:53:02.115 [error] Task #PID<0.266.0> started from #PID<0.265.0> terminating
** (ArgumentError) invalid ANSI sequence specification: :ok
    (elixir) lib/io/ansi.ex:167: IO.ANSI.format_sequence/1
    (elixir) lib/io/ansi.ex:218: IO.ANSI.do_format/5
    (hedwig) lib/hedwig/adapters/console.ex:109: Hedwig.Adapters.Console.Connection.print/1
    (hedwig) lib/hedwig/adapters/console.ex:87: Hedwig.Adapters.Console.Connection.loop/3
    (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: &Hedwig.Adapters.Console.Connection.loop/3
    Args: [#PID<0.265.0>, "florian", "Edgar Bot"]

Not sure I'm on the good path. Also, if you think this issue is getting nowhere, or should be discussed elsewhere, don't hesitate to tell me :)

PS: I've locally modified the console adapter to do like the test one, i.e registering itself :after_init, so that i don't get an :undefined pid.

scrogson commented 8 years ago

The bot is now registered in the console adapter under the :name field in the config.

https://github.com/hedwig-im/hedwig/commit/5f91e1d311319045cdc4e5b6ce53f036710e0525

scrogson commented 8 years ago

Ok...so I"ve been able to get this all working...

Hedwig Console - press Ctrl+C to exit.

The console adapter is useful for quickly verifying how your
bot will respond based on the current installed responders

scrogson> alfred hey
alfred> sup?
scrogson> alfred !!
alfred> sup?
scrogson> 

Here's the NotNot responder:

defmodule Alfred.Responders.NotNot do
  use Hedwig.Responder

  respond ~r/(.*)/i, msg do
    case msg.matches[1] do
      "!!" -> ""
      _ -> Alfred.Brain.put(:lastmsg, msg)
    end
  end

  respond ~r/!!/i, _msg do
    msg = Alfred.Brain.get(:lastmsg)
    pid = Hedwig.whereis("alfred")
    Hedwig.Robot.handle_message(pid, msg)
  end
end
docteurklein commented 8 years ago

Awesome! many thanks. Closing now.