robut
robut copied to clipboard
Trying to figure out how to get robut to join rooms when invited
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?
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!
Thanks for looking into it. Terrible is a great start. I'll give this a spin soon
Been using this for a long time now it's great
The only thing, on a private server (https://github.com/justinweiss/robut/pull/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
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.
Also, holy crap March!? Where did the year go
@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 method
connection' for main:Object (NameError)
Do you have a working sample of bin/robut ??
Lucky me, I somehow still had my uncommitted code on this machine! I'll push it to a branch.
Thanks so much
On Oct 14, 2016, at 13:06, Justin Weiss [email protected] 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.
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.
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
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 [email protected] 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.
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 [email protected] 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 [email protected] 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 https://github.com/justinweiss/robut/issues/41#issuecomment-253930339, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAD_Nh2oVIUquJqCIKEG0MpDJW4DKIjks5qz_u6gaJpZM4Bn4n9.