muc_room_config.rb [plain text]
#!/usr/bin/ruby -w
# == Synopsis
#
# This tool will create, configure, and destroy Jabber multi-user chat rooms (XEP-0045). It is designed to work with
# Apple's version of mu-conference 0.62. It needs to login to the Jabber server as a client, so valid authentication
# credentials are required. A custom XMPP4R library must be installed which works around some bugs in the present XMPP4R trunk.
#
# == Usage
# muc_room_config.rb [options] <command> <command options>
# Environment:
# Authentication credentials (username and password) must be passed in using these environment variables. These are
# REQUIRED:
# MUC_USER : Username for Jabber login
# MUC_PASS : Password for Jabber login
#
# Options:
# -D, --debug: Enable debug mode
# -o, --hostname: Jabber server hostname (REQUIRED)
# -r, --roomname: Name of Jabber MUC room to create/modify/use (REQUIRED)
#
# Commands:
# Choose one command per execution:
# -c, --create: Create and configure a MUC room.
# usage: -c
# -i, --invite: Sends chat room invitations to 1 or more JIDs. The room must
# already be created and configured.
# usage: -i <JID> (<JID> <JID> ...)
# -d, --destroy: Destroy a room (not yet implemented)
# -h, -?, --help: This usage information
# --default: Display the default room configuration and exit
#
# Additional room configuration options:
# Use the following arguments to specify non-default options for room configuration (for use with -c command):
# Room Description:
# --roomdesc <string>
# Leave message (displayed when a user exits the room):
# --leave <string>
# Join message (displayed when a user joins the room):
# --join <string>
# Message for user renaming nickname in room:
# --rename <string>
# Allow Occupants to Change Subject:
# --changesubject <true | false>
# Maximum Number of Room Occupants (0 for unlimited):
# --maxusers <number>
# Allow Occupants to query other Occupants:
# --privacy <true | false>
# Allow Public Searching for Room:
# --publicroom <true | false>
# Make Room Persistent:
# --persistentroom <true | false>
# Consider all Clients as Legacy (shown messages):
# --legacy <true | false>
# Make Room Moderated (By default, new users entering a moderated room are only visitors):
# --moderatedroom <true | false>
# Make Occupants in a Moderated Room Default to Participant:
# --defaulttype <true | false>
# Ban Private Messages between Occupants:
# --privmsg <true | false>
# An Invitation is Required to Enter:
# --inviteonly <true | false>
# Allow Occupants to Invite Others:
# --allowinvites <true | false>
# A Password is required to enter?
# --passwordprotected <true | false>
# The Room Password:
# --roomsecret <string>
# Affiliations that May Discover Real JIDs of Occupants:
# --whois <true | false>
# Enable Logging of Room Conversations:
# --enablelogging <true | false>
# Logfile format:
# --logformat <text | xml | xhtml>
#
#
# Examples:
# env MUC_USER="admin" MUC_PASS="let_me_in" ruby muc_room_config.rb -o chatserver.example.com -r my_room -c --enablelogging true --logformat text --join "Welcome to my_room, enjoy your stay"
# env MUC_USER="admin" MUC_PASS="let_me_in" ruby muc_room_config.rb -o chatserver.example.com -r my_room -i bob@chatserver.example.com alice@chatserver.example.com
# env MUC_USER="admin" MUC_PASS="let_me_in" ./muc_room_config.rb -o chatserver.example.com -r my_room -d
#
# == Author
# Joel Hedden (jhedden@apple.com)
#
# == Copyright
# Copyright 2007 Apple Inc. All rights reserved.
#
# == State
# Functional, unfinished
#
# == Todo
# * error handling
# * list/grant/revoke room ownership
# * add logging mechanism
#
require 'xmpp4r'
require 'xmpp4r/muc'
#require 'xmpp4r/muc/x'
require 'rdoc/usage'
include Jabber
include MUC
#################
# MucRoomTool Class
class MucRoomTool
# Define constants
TOOL_VERSION = "0.1"
RESOURCE = 'RubyXMPP4R'
DESTROY_REASON = "Your workgroup has been deleted."
INVITE_REASON = "You have been automatically invited to this chatroom for an OD workgroup."
BOOL = { 'true' => 1, 'false' => 0 }
def initialize
# Set default room configuration
@room_config = {
'FORM_TYPE' => 'http://jabber.org/protocol/muc#roomconfig',
'form' => 'config',
'muc#owner_roomname' => nil,
'muc#owner_roomdesc' => nil,
'leave' => 'has left',
'join' => 'has become available',
'rename' => 'is now known as',
'muc#owner_changesubject' => 0,
'muc#owner_maxusers' => 0,
'privacy' => 0,
'muc#owner_publicroom' => 1,
'muc#owner_persistentroom' => 1,
'legacy' => 0,
'muc#owner_moderatedroom' => 0,
'defaulttype' => 0,
'privmsg' => 0,
'muc#owner_inviteonly' => 0,
'muc#owner_allowinvites' => 0,
'muc#owner_passwordprotectedroom' => 0,
'muc#owner_roomsecret' => nil,
'muc#owner_whois' => 'admins',
'muc#owner_enablelogging' => 0,
'logformat' => 'xml'
}
# Initialize instance variables
@debug = false
@username = nil
@password = nil
@roomname = nil
@hostname = nil
@client = nil
@invite_list = nil
@invite = false
@create = false
@destroy = false
@reason = nil
@alt_roomname = nil
@room_jid = nil
@client_jid = nil
@client_full_jid = nil
@client_muc_jid = nil
end
def parse_options
require 'getoptlong'
opts = GetoptLong.new(
# Commands
[ '--help', '-h', '-?', GetoptLong::NO_ARGUMENT ],
[ '--default', GetoptLong::NO_ARGUMENT ],
[ '--create', '-c', GetoptLong::NO_ARGUMENT ],
[ '--invite', '-i', GetoptLong::NO_ARGUMENT ],
[ '--destroy', '-d', GetoptLong::NO_ARGUMENT ],
# Options
[ '--debug', '-D', GetoptLong::NO_ARGUMENT ],
[ '--hostname', '-o', GetoptLong::REQUIRED_ARGUMENT ],
[ '--roomname', '-r', GetoptLong::REQUIRED_ARGUMENT ],
[ '--reason', '-R', GetoptLong::REQUIRED_ARGUMENT ],
# Optional args for room configuration:
[ '--roomdesc', GetoptLong::REQUIRED_ARGUMENT ],
[ '--leave', GetoptLong::REQUIRED_ARGUMENT ],
[ '--join', GetoptLong::REQUIRED_ARGUMENT ],
[ '--rename', GetoptLong::REQUIRED_ARGUMENT ],
[ '--changesubject', GetoptLong::REQUIRED_ARGUMENT ],
[ '--maxusers', GetoptLong::REQUIRED_ARGUMENT ],
[ '--privacy', GetoptLong::REQUIRED_ARGUMENT ],
[ '--publicroom', GetoptLong::REQUIRED_ARGUMENT ],
[ '--persistentroom', GetoptLong::REQUIRED_ARGUMENT ],
[ '--legacy', GetoptLong::REQUIRED_ARGUMENT ],
[ '--moderatedroom', GetoptLong::REQUIRED_ARGUMENT ],
[ '--defaulttype', GetoptLong::REQUIRED_ARGUMENT ],
[ '--privmsg', GetoptLong::REQUIRED_ARGUMENT ],
[ '--inviteonly', GetoptLong::REQUIRED_ARGUMENT ],
[ '--allowinvites', GetoptLong::REQUIRED_ARGUMENT ],
[ '--passwordprotectedroom', GetoptLong::REQUIRED_ARGUMENT ],
[ '--roomsecret', GetoptLong::REQUIRED_ARGUMENT ],
[ '--whois', GetoptLong::REQUIRED_ARGUMENT ],
[ '--enablelogging', GetoptLong::REQUIRED_ARGUMENT ],
[ '--logformat', GetoptLong::REQUIRED_ARGUMENT ]
)
opts.each do |opt, arg|
case opt
# Commands
when '--help'
RDoc::usage('usage')
when '--default'
display_default_room_config
exit 0
when '--create'
@create = true
when '--invite'
@invite = true
when '--destroy'
@destroy = true
# Options
when '--debug'
@debug = true
Jabber::debug = true
when '--hostname'
@hostname = arg
when '--roomname'
@roomname = arg
when '--reason'
@reason = arg
# Optional args for room configuration:
when '--roomdesc'
@room_config['muc#owner_roomdesc'] = arg
when '--leave'
@room_config['leave'] = arg
when '--join'
@room_config['join'] = arg
when '--rename'
@room_config['rename'] = arg
when '--changesubject'
@room_config['muc#owner_changesubject'] = BOOL[arg]
when '--maxusers'
@room_config['muc#owner_maxusers'] = arg
when '--privacy'
@room_config['privacy'] = BOOL[arg]
when '--publicroom'
@room_config['muc#owner_publicroom'] = BOOL[arg]
when '--persistentroom'
@room_config['muc#owner_persistentroom'] = BOOL[arg]
when '--legacy'
@room_config['legacy'] = BOOL[arg]
when '--moderatedroom'
@room_config['muc#owner_moderatedroom'] = BOOL[arg]
when '--defaulttype'
@room_config['defaulttype'] = BOOL[arg]
when '--privmsg'
@room_config['privmsg'] = BOOL[arg]
when '--inviteonly'
@room_config['muc#owner_inviteonly'] = BOOL[arg]
when '--allowinvites'
@room_config['muc#owner_allowinvites'] = BOOL[arg]
when '--passwordprotectedroom'
@room_config['muc#owner_passwordprotectedroom'] = BOOL[arg]
when '--roomsecret'
@room_config['muc#owner_roomsecret'] = arg
when '--whois'
@room_config['muc#owner_whois'] = arg
when '--enablelogging'
@room_config['muc#owner_enablelogging'] = BOOL[arg]
when '--logformat'
@room_config['logformat'] = arg
end
end
# JJJ debugging: @room_config.each {|key, value| puts "#{key} is #{value}"}
# if --invite was specified, load the remaining args into a JID array
if (@invite)
@invite_list = ARGV
end
# Get authentication args from environment
@username = ENV['MUC_USER']
@password = ENV['MUC_PASS']
# Configure variables that are based on user input
if (@username && @password && @hostname && RESOURCE && @roomname)
@room_jid = "#{@roomname}@conference.#{@hostname}"
@client_jid = "#{@username}@#{@hostname}"
@client_full_jid = "#{@client_jid}/#{RESOURCE}"
@client_muc_jid = "#{@room_jid}/#{@username}"
@room_config['muc#owner_roomname'] = @roomname
if (! @room_config['muc#owner_roomdesc'])
@room_config['muc#owner_roomdesc'] = @roomname
end
@alt_roomname = "#{@roomname}_alt@conference.#{@hostname}"
else
puts "Some required arguments were not provided. See usage:\n"
RDoc::usage_no_exit('usage')
exit 1
end
end
# This is rewritten from MUC::MUCClient::Configure to work around a couple of bugs:
# 1. original code calls stream.send_with_id(iq) which returns NIL
# 2. original code sends the wrong JID when requesting a config form, so it failed
def configure_room(client, muc, room_jid, options={})
raise 'You are not the owner' unless muc.owner?
iq = Iq.new(:get, room_jid)
iq.to = room_jid
iq.from = muc.my_jid
iq.add(IqQueryMUCOwner.new)
fields = []
#answer = client.send_with_id(iq)
#raise "Configuration not possible for this room" unless answer.query && answer.query.x(XData)
#answer.query.x(XData).fields.each { |field|
# if (var = field.attributes['var'])
# fields << var
# end
#}
#JJJ send_with_id was returning NIL (when an answer was available) so the above failed.
# The following may not raise an exception if we get no answer.
client.send_with_id(iq)
# fill out the reply form
iq = Iq.new(:set, room_jid)
iq.to = room_jid
iq.from = muc.my_jid
query = IqQueryMUCOwner.new
form = Dataforms::XData.new
form.type = :submit
options.each do |var, values|
field = Dataforms::XDataField.new
values = [values] unless values.is_a?(Array)
field.var, field.values = var, values
form.add(field)
end
query.add(form)
iq.add(query)
client.send_with_id(iq)
return true
end
def destroy_room(client, client_full_jid, room_jid, reason, alt_room_jid)
iq = Iq.new(:set, room_jid)
iq.to = room_jid
iq.from = client_full_jid
query = IqQueryMUCOwner.new
elem_destroy = REXML::Element::new('destroy')
if (alt_room_jid)
elem_destroy.attributes['jid'] = alt_room_jid
end
if (reason)
elem_reason = REXML::Element::new('reason').add_text(reason)
elem_destroy.add_element(elem_reason)
end
query.add(elem_destroy)
iq.add(query)
client.send_with_id(iq)
return true
end
def join_room(client, client_muc_jid, client_full_jid)
muc = MUCClient.new(client)
muc.my_jid = client_full_jid
muc.join(client_muc_jid)
muc
end
def connect_client(client_full_jid, password)
client = Client.new(client_full_jid)
client.connect
client.auth_nonsasl(password, false)
client.send(Presence::new)
client
end
def send_invites(client, client_jid, room_jid, reason, recipients)
recipients.each { |jid|
msg = Message.new
msg.from = client_jid
msg.to = room_jid
x = msg.add(XMUCUser.new)
x.add(XMUCUserInvite.new(jid, reason))
client.send(msg)
}
return true
end
def display_default_room_config
puts "Default room configuration (room name and description are configured by the -r argument):\n"
@room_config.each {|key, value| puts "#{key} = #{value}"}
end
#### Unfinished
def room_exists?()
end
#
def get_room_owners()
end
#
def grant_room_ownership()
end
#
def revoke_room_ownership()
end
####
########################################################################
# Execute: Perform operations based on user input options
def execute
client = connect_client(@client_full_jid, @password)
### Perform the user-requested operation(s)
# Create a room
if (@create && @roomname)
puts "Creating room #{@roomname}...\n"
#if room_exists?(@roomname)
muc = join_room(client, @client_muc_jid, @client_full_jid)
configure_room(client, muc, @room_jid, @room_config)
puts "Done creating and configuring room #{@roomname}.\n"
end
# Invite users to a room (room must exist!)
if (@invite && @roomname && @invite_list)
puts "Joining room #{@roomname}...\n"
muc = join_room(client, @client_muc_jid, @client_full_jid)
puts "Sending chat room invitations...\n"
send_invites(client, @client_full_jid, @room_jid, (@reason != nil) ? @reason : INVITE_REASON, @invite_list)
puts "Done sending chat room invitations.\n"
end
# Destroy a room
if (@destroy && @roomname)
puts "Destroying room #{@roomname}...\n"
join_room(client, @client_muc_jid, @client_full_jid)
destroy_room(client, @client_full_jid, @room_jid, (@reason != nil) ? @reason : DESTROY_REASON, @alt_roomname)
puts "Done destroying room #{@roomname}...\n"
end
return true
end
end # class MucRoomTool
# JJJ: get completed room config form (create a method...)
#iq = Iq.new(:get, $room_jid)
#iq.from = $client_full_jid
#query = IqQueryMUCOwner.new
#iq.add(query)
#$client.send_with_id(iq)
#################
# MAIN
muc_room_tool = MucRoomTool.new
muc_room_tool.parse_options
status = muc_room_tool.execute ? 0 : 1
exit status