Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to figure out how to get robut to join rooms when invited #41

Open
will opened this issue Mar 7, 2014 · 13 comments
Open

Trying to figure out how to get robut to join rooms when invited #41

will opened this issue Mar 7, 2014 · 13 comments

Comments

@will
Copy link
Contributor

will commented Mar 7, 2014

I'd like to have the bot appear in rooms when you @ mention it.

I've figured out how to notice the invite, but joining the room after Connection.new.connect doesn't seem to work

Invitaitons look like

 <message from='(room name)@conf.hipchat.com' to='(id)@chat.hipchat.com'><x xmlns='http://jabber.org/protocol/muc#user'><invite from='(id)@chat.hipchat.com/osx'><reason>@DumboTBot some message</reason></invite></x><x xmlns='http://hipchat.com/protocol/muc#room'><name>room name</name><topic>The topic of the room</topic><privacy>public</privacy></x></message>

Notably the message does not have a type.

I was able to just quick hack into the PM class to print something when invited:

class Robut::PM < Robut::Presence
  def initialize(connection, rooms)
    # Add the callback from direct messages. Turns out the
    # on_private_message callback doesn't do what it sounds like, so I
    # have to go a little deeper into xmpp4r to get this working.
    self.connection = connection
    connection.client.add_message_callback(200, self) do |message|
      invited(message, connection)
     # ... rest of that method
  end

  def invited(message, conn)
    if message.type.nil?
      p message.to_s
      if message.to_s =~ /invite/ # because I didn't want to figure out how to traverse to see the <invite> just to see if this worked at all
        begin
          room_name = "#{message.from.node}@conf.hipchat.com"
          puts "joining #{room_name}"

        room = Robut::Room.new(conn, message.from.node + '@conf.hiphcat.com')
        room.join
        room.reply("I am here", nil)

        rescue => e
         p e
        end
      end
    end         

The Room.new and the #join don't raise exceptions, but the bot never joins the room.
Any ideas how to do this?

@justinweiss
Copy link
Owner

OK! I figured this out.

It turns out that you can't join rooms from within an xmpp4r callback. So you'll have to somehow flag that robut wants to join a room from within the callback, and actually join the room from the main thread. I was able to hack it into working by doing something like:

in pm.rb

  def invited(message, conn)
    if message.type.nil?
      p message.to_s
      if message.to_s =~ /invite/ # because I didn't want to figure out how to traverse to see the <invite> just to see if this worked at all
        begin
          room_name = "#{message.from.node}@conf.hipchat.com"
          puts "joining #{room_name}"
          connection.joins = room_name
        rescue => e
          p e
        end
      end
    end
  end

in connection.rb

  attr_accessor :joins

  def handle_join
    self.config.logger.info 'trying join...'
    if self.joins
      room = Robut::Room.new(self,  self.joins)
      room.join
      self.rooms << room
      room.reply("I am here", nil)
      self.joins = nil
    end

in bin/robut

loop { connection.handle_join; sleep 1 }

But this is pretty clearly terrible. I'm sure there's a way to do it relatively cleanly, though!

@will
Copy link
Contributor Author

will commented Mar 19, 2014

Thanks for looking into it. Terrible is a great start. I'll give this a spin soon

@will
Copy link
Contributor Author

will commented Nov 5, 2014

Been using this for a long time now it's great

@will
Copy link
Contributor Author

will commented Nov 6, 2014

The only thing, on a private server (#47)

room_name = "#{message.from.node}@conf.hipchat.com"

has to be changed to

room_name = "#{message.from.node}@conf.btf.hipchat.com"

or, when mentioned in another room robut will crash

@justinweiss
Copy link
Owner

Nice! This has been on my list of things to follow up on. The change works, but I wanted to write some tests for it, and couldn't figure out how to fake XMPP messages to try it out.

@justinweiss
Copy link
Owner

Also, holy crap March!? Where did the year go

@gerardepema
Copy link

gerardepema commented Oct 13, 2016

@justinweiss, @will , I'm working on the same thing as you did to get a bot join a channel on invite. I've got most of it working. I'm seeing the "joining message" (nice work on that) on invites but I'm having issues with adding the loop part to /bin/robut..
I'm getting this message: block in <main>': undefined local variable or methodconnection' for main:Object (NameError)

Do you have a working sample of bin/robut ??

@justinweiss
Copy link
Owner

Lucky me, I somehow still had my uncommitted code on this machine! I'll push it to a branch.

@gerardepema
Copy link

Thanks so much

On Oct 14, 2016, at 13:06, Justin Weiss notifications@github.com wrote:

Lucky me, I somehow still had my uncommitted code on this machine! I'll push it to a branch.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

@justinweiss
Copy link
Owner

OK, give https://github.com/justinweiss/robut/tree/accept_invites a try. I don't remember quite what state it was in, but I think it should be working.

@gerardepema
Copy link

The supplied code throws this error:

/home/y/lib64/ruby/gems/2.3.0/gems/robut-0.5.2/lib/robut/pm.rb:3:in `initialize': wrong number of arguments (given 2, expected 1) (ArgumentError)
    from /home/y/lib64/ruby/gems/2.3.0/gems/robut-0.5.2/lib/robut/connection.rb:96:in `new'
    from /home/y/lib64/ruby/gems/2.3.0/gems/robut-0.5.2/lib/robut/connection.rb:96:in `connect'
    from /home/y/bin64/robut_new:15:in `<main>'

Here's my working config:
bin/robut

#!/usr/bin/env ruby
require 'rubygems'
begin
  require 'robut'
rescue LoadError
  require 'bundler/setup'
  require 'robut'
end
require 'ostruct'
require 'logger'

load ARGV[0] || './Chatfile'

connection = Robut::Connection.new.connect
loop do
  connection.handle_join
  sleep 1
end

pm.rb

class Robut::PM < Robut::Presence

  def initialize(connection, rooms)
    # Add the callback from direct messages. Turns out the
    # on_private_message callback doesn't do what it sounds like, so I
    # have to go a little deeper into xmpp4r to get this working.
    self.connection = connection
    connection.client.add_message_callback(200, self) do |message|
      invited(message, connection)
      from_room = rooms.any? {|room| room.muc.from_room?(message.from)}
      if !from_room && message.type == :chat && message.body
        time = Time.now # TODO: get real timestamp? Doesn't seem like
                        # jabber gives it to us
        sender_jid = message.from
        plugins = Robut::Plugin.plugins.map { |p| p.new(self, sender_jid) }
        handle_message(plugins, time, connection.roster[sender_jid].iname, message.body)
        true
      else
        false
      end
    end
  end

  def invited(message, conn)
    if message.type.nil?
      p message.to_s
      if message.to_s =~ /invite/ # because I didn't want to figure out how to traverse to see the <invite> just to see if this worked at all
        begin
          # Uncomment the below line if you have you're a private server and comment the room_name on the line below.
          # room_name = "#{message.from.node}@conf.btf.hipchat.com"
          room_name = "#{message.from.node}@conf.hipchat.com"
          puts "joining #{room_name}"
          connection.joins = room_name
        rescue => e
          p e
        end
      end
    end
  end

  def reply(message, to)
    unless to.kind_of?(Jabber::JID)
      to = find_jid_by_name(to)
    end

    msg = Jabber::Message.new(to, message)
    msg.type = :chat
    connection.client.send(msg)
  end

  private

  # Find a jid in the roster with the given name, case-insensitively
  def find_jid_by_name(name)
    name = name.downcase
    connection.roster.items.detect {|jid, item| item.iname.downcase == name}.first
  end
end

connection.pm

require 'xmpp4r'
require 'xmpp4r/muc/helper/simplemucclient'
require 'xmpp4r/roster/helper/roster'
require 'ostruct'

if defined?(Encoding)
  # Monkey-patch an incompatibility between ejabberd and rexml
  require 'rexml_patches'
end

# Handles opening a connection to the HipChat server, and feeds all
# messages through our Robut::Plugin list.
class Robut::Connection

  # The configuration used by the Robut connection.
  #
  # Parameters:
  #
  # [+jid+, +password+, +nick+] The HipChat credentials given on
  #                             https://www.hipchat.com/account/xmpp
  #
  # [+rooms+] The chat room(s) to join, with each in the format <tt>jabber_name</tt>@<tt>conference_server</tt>
  #
  # [+logger+] a logger instance to use for debug output.
  attr_accessor :config

  # The Jabber::Client that's connected to the HipChat server.
  attr_accessor :client

  # The storage instance that's available to plugins
  attr_accessor :store

  # The roster of currently available people
  attr_accessor :roster

  # The rooms that robut is connected to.
  attr_accessor :rooms

  class << self
    # Class-level config. This is set by the +configure+ class method,
    # and is used if no configuration is passed to the +initialize+
    # method.
    attr_accessor :config
  end

  attr_accessor :joins

  def handle_join
    if self.joins
      puts 'trying join...'
      room = Robut::Room.new(self,  self.joins)
      room.join
      self.rooms << room
      room.reply("I was summoned here??", nil)
      self.joins = nil
    end
  end

  # Configures the connection at the class level. When the +robut+ bin
  # file is loaded, it evals the file referenced by the first
  # command-line parameter. This file can configure the connection
  # instance later created by +robut+ by setting parameters in the
  # Robut::Connection.configure block.
  def self.configure
    self.config = OpenStruct.new
    yield config
  end

  # Sets the instance config to +config+, converting it into an
  # OpenStruct if necessary.
  def config=(config)
    @config = config.kind_of?(Hash) ? OpenStruct.new(config) : config
  end

  # Initializes the connection. If no +config+ is passed, it defaults
  # to the class_level +config+ instance variable.
  def initialize(_config = nil)
    self.config = _config || self.class.config

    self.client = Jabber::Client.new(self.config.jid)
    self.store = self.config.store || Robut::Storage::HashStore # default to in-memory store only
    self.config.rooms ||= Array(self.config.room) # legacy support?
    self.config.enable_private_messaging = true if self.config.enable_private_messaging.nil?

    if self.config.logger
      Jabber.logger = self.config.logger
      Jabber.debug = true
    end
  end

  # Connects to the specified room with the given credentials, and
  # enters an infinite loop. Any messages sent to the room will pass
  # through all the included plugins.
  def connect
    client.connect
    client.auth(config.password)
    client.send(Jabber::Presence.new.set_type(:available))

    self.roster = Jabber::Roster::Helper.new(client)
    roster.wait_for_roster

    self.rooms = self.config.rooms.collect do |room_name|
      Robut::Room.new(self, room_name).tap {|r| r.join }
    end

    if self.config.enable_private_messaging
      Robut::PM.new(self, rooms)
    end

    trap_signals
    self
  end

  # Send a message to all rooms.
  def reply(*args, &block)
    self.rooms.each do |room|
      room.reply(*args, &block)
    end
  end

private
  # Since we're entering an infinite loop, we have to trap TERM and
  # INT. If something like the Rdio plugin has started a server that
  # has already trapped those signals, we want to run those signal
  # handlers first.
  def trap_signals
    old_signal_callbacks = {}
    signal_callback = Proc.new do |signal|
      old_signal_callbacks[signal].call if old_signal_callbacks[signal]
      exit
    end

    [:INT, :TERM].each do |sig|
      old_signal_callbacks[sig] = trap(sig) { signal_callback.call(sig) }
    end
  end
end

I tried pushing this change to this branch but I don't have permissions

@gerardepema
Copy link

Hi Justin,

You code example throws some errors. I added it to the branch.
I was able to get it to work through your sample with some mods.
I included the files in a comment on the branch

Thanks so much for you help

Gerard

On Oct 14, 2016, at 13:13, Justin Weiss notifications@github.com wrote:

OK, give https://github.com/justinweiss/robut/tree/accept_invites a try. I don't remember quite what state it was in, but I think it should be working.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

@justinweiss
Copy link
Owner

Thanks! Sorry it wasn’t totally working — it’s been a while since I’ve had the chance to work on it.

On Oct 14, 2016, at 3:01 PM, gerardepema notifications@github.com wrote:

Hi Justin,

You code example throws some errors. I added it to the branch.
I was able to get it to work through your sample with some mods.
I included the files in a comment on the branch

Thanks so much for you help

Gerard

On Oct 14, 2016, at 13:13, Justin Weiss notifications@github.com wrote:

OK, give https://github.com/justinweiss/robut/tree/accept_invites a try. I don't remember quite what state it was in, but I think it should be working.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub #41 (comment), or mute the thread https://github.com/notifications/unsubscribe-auth/AAAD_Nh2oVIUquJqCIKEG0MpDJW4DKIjks5qz_u6gaJpZM4Bn4n9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants