cirrocumulus 0.4.6 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.6
1
+ 0.5.2
@@ -4,14 +4,14 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{cirrocumulus}
8
- s.version = "0.4.6"
7
+ s.name = "cirrocumulus"
8
+ s.version = "0.5.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Anton Kosyakin"]
12
- s.date = %q{2011-10-27}
13
- s.description = %q{Engine for building your own agents, providing you base functionality for loading ontologies, communicating with other agents and parsing FIPA-ACL messages}
14
- s.email = %q{deil@mneko.net}
12
+ s.date = "2011-12-01"
13
+ s.description = "Engine for building your own agents, providing you base functionality for loading ontologies, communicating with other agents and parsing FIPA-ACL messages"
14
+ s.email = "deil@mneko.net"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
17
17
  "README.rdoc"
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".document",
21
21
  "Gemfile",
22
- "Gemfile.lock",
23
22
  "LICENSE.txt",
24
23
  "README.rdoc",
25
24
  "Rakefile",
@@ -27,26 +26,26 @@ Gem::Specification.new do |s|
27
26
  "cirrocumulus.gemspec",
28
27
  "lib/cirrocumulus.rb",
29
28
  "lib/cirrocumulus/agent.rb",
29
+ "lib/cirrocumulus/agent_wrapper.rb",
30
30
  "lib/cirrocumulus/engine.rb",
31
31
  "lib/cirrocumulus/kb.rb",
32
32
  "lib/cirrocumulus/logger.rb",
33
- "lib/cirrocumulus/master_agent.rb",
34
33
  "lib/cirrocumulus/ontology.rb",
35
34
  "lib/cirrocumulus/rule_engine.rb",
36
35
  "lib/cirrocumulus/rule_server.rb",
36
+ "lib/cirrocumulus/rules/engine.rb",
37
+ "lib/cirrocumulus/rules/run_queue.rb",
37
38
  "lib/cirrocumulus/saga.rb",
38
- "lib/test.rb",
39
39
  "test/helper.rb",
40
40
  "test/test_cirrocumulus.rb"
41
41
  ]
42
- s.homepage = %q{http://github.com/deil/cirrocumulus}
42
+ s.homepage = "http://github.com/deil/cirrocumulus"
43
43
  s.licenses = ["GPL-2"]
44
44
  s.require_paths = ["lib"]
45
- s.rubygems_version = %q{1.3.7}
46
- s.summary = %q{Agent-based infrastructure management system}
45
+ s.rubygems_version = "1.8.10"
46
+ s.summary = "Agent-based infrastructure management system"
47
47
 
48
48
  if s.respond_to? :specification_version then
49
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
49
  s.specification_version = 3
51
50
 
52
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -54,7 +53,7 @@ Gem::Specification.new do |s|
54
53
  s.add_runtime_dependency(%q<log4r>, ["~> 1.1.9"])
55
54
  s.add_runtime_dependency(%q<systemu>, [">= 0"])
56
55
  s.add_runtime_dependency(%q<xmpp4r>, ["~> 0.5"])
57
- s.add_runtime_dependency(%q<xmpp4r-simple>, ["~> 0.8.8"])
56
+ s.add_runtime_dependency(%q<xmpp4r-simple>, [">= 0"])
58
57
  s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
59
58
  s.add_runtime_dependency(%q<deil_sexpistol>, [">= 0"])
60
59
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
@@ -65,7 +64,7 @@ Gem::Specification.new do |s|
65
64
  s.add_dependency(%q<log4r>, ["~> 1.1.9"])
66
65
  s.add_dependency(%q<systemu>, [">= 0"])
67
66
  s.add_dependency(%q<xmpp4r>, ["~> 0.5"])
68
- s.add_dependency(%q<xmpp4r-simple>, ["~> 0.8.8"])
67
+ s.add_dependency(%q<xmpp4r-simple>, [">= 0"])
69
68
  s.add_dependency(%q<eventmachine>, [">= 0"])
70
69
  s.add_dependency(%q<deil_sexpistol>, [">= 0"])
71
70
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
@@ -77,7 +76,7 @@ Gem::Specification.new do |s|
77
76
  s.add_dependency(%q<log4r>, ["~> 1.1.9"])
78
77
  s.add_dependency(%q<systemu>, [">= 0"])
79
78
  s.add_dependency(%q<xmpp4r>, ["~> 0.5"])
80
- s.add_dependency(%q<xmpp4r-simple>, ["~> 0.8.8"])
79
+ s.add_dependency(%q<xmpp4r-simple>, [">= 0"])
81
80
  s.add_dependency(%q<eventmachine>, [">= 0"])
82
81
  s.add_dependency(%q<deil_sexpistol>, [">= 0"])
83
82
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
@@ -1,4 +1,6 @@
1
- class Agent
1
+ require 'thread'
2
+
3
+ module Agent
2
4
  class AgentInfo
3
5
  attr_accessor :identifier
4
6
  attr_accessor :default_ontology
@@ -135,48 +137,106 @@ class Agent
135
137
  end
136
138
  end
137
139
 
138
- attr_reader :default_ontology
139
- attr_reader :identifier
140
- attr_reader :network_map
141
-
142
- def initialize(cm)
143
- @cm = cm
144
- @identifier = cm.jid
145
- @sagas = []
146
- @saga_idx = 0
147
- @network_map = NetworkMap.new(self)
148
- end
140
+ class Base
141
+ attr_reader :identifier
142
+ attr_reader :network_map
149
143
 
150
- def tick()
151
- @network_map.tick(@cm)
144
+ def initialize(cm)
145
+ Log4r::Logger['agent'].info('Initializing new agent')
152
146
 
153
- @sagas.each do |saga|
154
- next if saga.is_finished?
155
- saga.timeout -= 1 if saga.timeout > 0
156
- saga.handle(nil) if saga.timeout == 0
147
+ @cm = cm
148
+ @identifier = cm.jid
149
+ @ontologies = []
150
+ @message_queues = {}
151
+ @worker_threads = {}
152
+ #@network_map = NetworkMap.new(self)
157
153
  end
158
- end
159
-
160
- def restore_state()
161
- end
162
154
 
163
- def handles_ontology?(ontology)
164
- return @default_ontology == ontology || ontology == 'cirrocumulus-map'
165
- end
155
+ # Loads ontologies into agent
156
+ def load_ontologies(ontologies_list)
157
+ ontologies_list.each do |ontology_name|
158
+ ontology = eval("#{ontology_name}.new(self)")
159
+ @ontologies << ontology
160
+ @message_queues[ontology] = Queue.new
161
+ end
162
+ end
166
163
 
167
- def handle_message(message, kb)
168
- @network_map.handle_message(message, @cm)
164
+ def default_ontology
165
+ self.ontologies.size == 1 ? self.ontologies.first.name : nil
166
+ end
169
167
 
170
- if message.ontology != 'cirrocumulus-map'
171
- handle(message, kb)
168
+ # Returns true if this agents supports requested ontology
169
+ def handles_ontology?(ontology_name)
170
+ self.ontologies.any? {|ontology| ontology.name == ontology_name}
171
+ end
172
+
173
+ # Asks each loaded ontology to restore its state
174
+ def restore_state()
175
+ Log4r::Logger['agent'].info 'Restoring previous state..'
176
+
177
+ threads = []
178
+ self.ontologies.each do |ontology|
179
+ threads << Thread.new do
180
+ begin
181
+ ontology.restore_state()
182
+ rescue Exception => e
183
+ Log4r::Logger['agent'].warn "failed to restore state for ontology %s" % ontology.name
184
+ Log4r::Logger['agent'].warn "%s: %s" % [e.to_s, e.backtrace.to_s]
185
+ end
186
+ end
187
+ end
188
+
189
+ threads.each {|thrd| thrd.join}
190
+ Log4r::Logger['agent'].info 'State restored successfully!'
191
+ end
192
+
193
+ # Starts message processing threads
194
+ def start()
195
+ start_workers()
196
+ end
197
+
198
+ def send_message(message)
199
+ @cm.send_message(message)
200
+ end
201
+
202
+ def handle_message(message)
203
+ #@network_map.handle_message(message, @cm)
204
+
205
+ self.ontologies.each {|ontology|
206
+ if message.ontology == ontology.name || ontology.sagas.any? {|saga| saga.id == message.in_reply_to}
207
+ @message_queues[ontology] << message
208
+ break
209
+ end
210
+ }
211
+ end
212
+
213
+ protected
214
+
215
+ attr_reader :cm
216
+ attr_reader :ontologies
217
+
218
+ def start_workers()
219
+ self.ontologies.each do |ontology|
220
+ @worker_threads[ontology] = Thread.new do
221
+ Log4r::Logger['agent'].info "Starting worker for ontology %s" % [ontology.name]
222
+ process_ontology(ontology, @message_queues[ontology])
223
+ end
224
+ end
225
+ end
226
+
227
+ def process_ontology(ontology, queue)
228
+ while true do
229
+ message = queue.pop(true) rescue nil
230
+ begin
231
+ ontology.handle_incoming_message(message, nil) if message
232
+ rescue Exception => e
233
+ Log4r::Logger['agent'].warn "failed to handle incoming message for ontology %s: %s" % [ontology.name, e.to_s]
234
+ Log4r::Logger['agent'].warn e.backtrace.to_s
235
+ end
236
+
237
+ ontology.tick()
238
+ sleep 0.5
239
+ end
172
240
  end
173
- end
174
-
175
- def self.get_node(sender)
176
- sender.split('-').first
177
- end
178
-
179
- def self.get_subsystem(sender)
180
- sender.split('-').second
181
241
  end
182
242
  end
@@ -0,0 +1,68 @@
1
+ unless Kernel.respond_to?(:require_relative)
2
+ module Kernel
3
+ def require_relative(path)
4
+ require File.join(File.dirname(caller[0]), path.to_str)
5
+ end
6
+ end
7
+ end
8
+
9
+ require 'yaml'
10
+ require 'cirrocumulus'
11
+ require 'cirrocumulus/logger'
12
+ require 'cirrocumulus/engine'
13
+ require 'cirrocumulus/kb'
14
+ require 'cirrocumulus/ontology'
15
+ require 'cirrocumulus/agent'
16
+
17
+ class String
18
+ def underscore
19
+ self.gsub(/::/, '/').
20
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
21
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
22
+ tr("-", "_").
23
+ downcase
24
+ end
25
+ end
26
+
27
+ ontologies_file_name = nil
28
+
29
+ ARGV.each_with_index do |arg, i|
30
+ if arg == '-c'
31
+ ontologies_file_name = ARGV[i + 1]
32
+ end
33
+ end
34
+
35
+ if ontologies_file_name.nil?
36
+ puts "Please supply config file name"
37
+ exit(0)
38
+ end
39
+
40
+ puts "Loading configuration.."
41
+ agent_config = YAML.load_file(ontologies_file_name)
42
+ ontologies = agent_config['ontologies']
43
+ ontologies.each do |ontology_name|
44
+ puts "Will load ontology %s" % ontology_name
45
+ require File.join(AGENT_ROOT, 'ontologies', ontology_name.underscore)
46
+ end
47
+
48
+ kb_name = agent_config['kb']
49
+ kb = if kb_name
50
+ puts "Will load knowledge base %s" % kb_name
51
+ require File.join(AGENT_ROOT, 'ontologies/xen/', kb_name.underscore) # TODO
52
+ eval("#{kb_name}.new()")
53
+ else
54
+ Kb.new
55
+ end
56
+
57
+ cm = Cirrocumulus.new('master')
58
+ a = Agent::Base.new(cm)
59
+ a.load_ontologies(agent_config['ontologies'])
60
+ begin
61
+ cm.run(a, kb)
62
+ rescue Exception => e
63
+ puts 'Got an error:'
64
+ puts e
65
+ puts e.backtrace
66
+ end
67
+
68
+ puts "\nBye-bye."
@@ -1,9 +1,11 @@
1
1
  require 'systemu'
2
+ require 'thread'
2
3
 
3
4
  class Cirrocumulus
5
+ # Message. Used by agents to communicate
4
6
  class Message
5
7
  attr_accessor :sender, :act, :content
6
- attr_accessor :receiver, :reply_with, :in_reply_to
8
+ attr_accessor :receiver, :reply_with, :in_reply_to, :conversation_id
7
9
  attr_accessor :ontology
8
10
 
9
11
  def initialize(sender, act, content)
@@ -19,22 +21,58 @@ class Cirrocumulus
19
21
  def context
20
22
  Context.new(@sender, @reply_with)
21
23
  end
24
+
25
+ def self.parse_params(content, subroutine = false)
26
+ return parse_params(content.size == 1 ? content[0] : content, true) if !subroutine
27
+
28
+ return [] if content.nil?
29
+ return content if !content.is_a?(Array)
30
+ return [] if content.size == 0
31
+ return {content[0] => []} if content.size == 1
32
+ return {content[0] => parse_params(content[1], true)} if content.size == 2
33
+
34
+ res = {content[0] => []}
35
+
36
+ if content.all? {|item| !item.is_a?(Array)}
37
+ content.each_with_index do |item,i|
38
+ if i == 0
39
+ res[content[0]] = []
40
+ else
41
+ res[content[0]] << item
42
+ end
43
+ end
44
+ else
45
+ content.each_with_index do |item,i|
46
+ if i == 0
47
+ res[content[0]] = {}
48
+ else
49
+ res[content[0]].merge!(parse_params(item, true))
50
+ end
51
+ end
52
+ end
53
+
54
+ res
55
+ end
22
56
  end
23
57
 
58
+ # Message context. Includes sender, reply-with and conversation-id
24
59
  class Context
25
60
  attr_reader :sender
26
61
  attr_reader :reply_with
62
+ attr_reader :conversation_id
27
63
 
28
- def initialize(sender, reply_with)
64
+ def initialize(sender, reply_with, conversation_id = nil)
29
65
  @sender = sender
30
66
  @reply_with = reply_with
67
+ @conversation_id = conversation_id
31
68
  end
32
69
  end
33
70
 
71
+ # Gets current platform (linux, freebsd, etc)
34
72
  def self.platform
35
- if PLATFORM =~ /freebsd/
73
+ if RUBY_PLATFORM =~ /freebsd/
36
74
  return 'freebsd'
37
- elsif PLATFORM =~ /linux/
75
+ elsif RUBY_PLATFORM =~ /linux/
38
76
  return 'linux'
39
77
  end
40
78
 
@@ -47,20 +85,15 @@ class Cirrocumulus
47
85
  Log4r::Logger['cirrocumulus'].info 'platform: ' + Cirrocumulus::platform
48
86
  @suffix = suffix
49
87
  @generate_jid = generate_jid
88
+ @send_queue = Queue.new
50
89
  end
51
90
 
52
- def send(message)
53
- msg = "<fipa-message ontology=\"#{message.ontology}\""
54
- msg += " receiver=\"#{message.receiver}\"" if message.receiver
55
- msg += " act=\"#{message.act}\""
56
- msg += " reply-with=\"#{message.reply_with}\"" if message.reply_with
57
- msg += " in-reply-to=\"#{message.in_reply_to}\"" if message.in_reply_to
58
- message_content = message.content if message.content.is_a? String
59
- message_content = Sexpistol.new.to_sexp(message.content) if message.content.is_a? Array
60
- msg += "><content>#{message_content}</content></fipa-message>"
61
- @im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')}</body></message>")
91
+ # Sends message to other agents
92
+ def send_message(message)
93
+ @send_queue << message
62
94
  end
63
95
 
96
+ =begin
64
97
  def inform(receiver, answer, ontology = nil)
65
98
  msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"inform\"><content>#{answer}</content></fipa-message>"
66
99
  @im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')}</body></message>")
@@ -80,82 +113,80 @@ class Cirrocumulus
80
113
  msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"request\"><content>#{action}</content></fipa-message>"
81
114
  @im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')}</body></message>")
82
115
  end
116
+ =end
83
117
 
118
+ # Main loop. Connects to other agents and processes all incoming messages
84
119
  def run(agent, kb, sniff = false)
85
120
  connect(generate_jid(agent))
86
121
 
87
122
  Log4r::Logger['cirrocumulus'].info("entering main loop")
88
123
  agent.restore_state()
89
-
124
+ agent.start()
125
+
90
126
  s = Sexpistol.new
127
+ should_be_running = true
91
128
 
92
- loop do
129
+ while should_be_running do
93
130
  kb.collect_knowledge()
94
-
131
+
95
132
  begin
96
- connect(generate_jid(agent)) if @im.nil? || !@im.connected?
133
+ connect(generate_jid(agent)) if !connected?
97
134
  rescue Exception => e
98
135
  puts e.to_s
99
136
  sleep 5
100
137
  end
101
-
102
- next if @im.nil? || !@im.connected?
103
138
 
139
+ next if !connected?
140
+
141
+ # process incoming messages from queue
104
142
  @im.received_messages do |message|
105
- next if !message.x('jabber:x:delay').nil?
106
-
107
- begin
108
- xml = Hash.from_xml(message.body)['fipa_message']
109
-
110
- ontology = xml['ontology']
111
- sender = message.from.resource
112
- receiver = xml['receiver']
113
- supported_ontology = agent.handles_ontology?(ontology)
114
- if (supported_ontology && (receiver == @jid || receiver.blank?)) || receiver == @jid || sniff
115
- act = xml['act']
116
- content_raw = xml['content']
117
- content = s.parse_string(content_raw)
118
- msg = Cirrocumulus::Message.new(sender, act, content)
119
- msg.receiver = receiver
120
- msg.reply_with = xml['reply_with']
121
- msg.in_reply_to = xml['in_reply_to']
122
- msg.ontology = ontology
123
- flatten_message_content(msg)
124
- Log4r::Logger['cirrocumulus'].debug(msg.inspect)
125
- agent.handle_message(msg, kb)
126
- else
127
- Log4r::Logger['cirrocumulus'].debug("unhandled ontology %s" % [ontology])
128
- end
129
- rescue Exception => e
130
- puts e.to_s
131
- puts e.backtrace
132
- end
143
+ process_incoming_message(agent, kb, message, s, sniff)
133
144
  end
134
145
 
135
- agent.tick()
136
- sleep 0.5
146
+ # send all queued messages
147
+ process_send_queue()
148
+
149
+ sleep 0.5 # TODO: do we need this stuff?
137
150
  end
138
151
 
139
152
  @im.disconnect
140
153
  end
141
-
154
+
142
155
  private
143
-
156
+
157
+ # Flatterns incoming message if is bounded into multiple arrays
158
+ def flatten_message_content(message)
159
+ if !message.content.is_a?(Array)
160
+ message.content = [message.content]
161
+ else
162
+ while message.content.is_a?(Array) && message.content.size == 1 && message.content.first.is_a?(Array)
163
+ message.content = message.content.first
164
+ end
165
+ end
166
+ end
167
+
168
+ # Generates JID for self identification
144
169
  def generate_jid(agent)
145
170
  suffix = agent.default_ontology ? agent.default_ontology.gsub('cirrocumulus-', '') : @suffix
146
171
  if @generate_jid
147
- _, hostname = systemu 'hostname'
172
+ _, hostname = systemu('hostname')
148
173
  hostname.strip!
149
174
  "%s-%s" % [hostname, suffix]
150
175
  else
151
176
  suffix
152
177
  end
153
178
  end
154
-
179
+
180
+ # Checks if agent is connected to Jabber conference
181
+ def connected?
182
+ @im && @im.connected?
183
+ end
184
+
185
+ # Connects to Jabber server and joins the conference
155
186
  def connect(jid)
156
187
  @jid = jid
157
188
  Log4r::Logger['cirrocumulus'].info "logging as " + @jid
158
-
189
+
159
190
  begin
160
191
  @im.disconnect if @im && @im.connected?
161
192
  full_jid = @jid + "@" + JABBER_SERVER
@@ -163,7 +194,7 @@ class Cirrocumulus
163
194
  rescue Jabber::ClientAuthenticationFailure => ex
164
195
  Log4r::Logger['cirrocumulus'].warn ex.to_s
165
196
  Log4r::Logger['cirrocumulus'].info "registering new account with default password"
166
-
197
+
167
198
  client = Jabber::Client.new(full_jid)
168
199
  client.connect()
169
200
  client.register(JABBER_DEFAULT_PASSWORD) #, {'username' => full_jid, 'password' => JABBER_DEFAULT_PASSWORD})
@@ -179,14 +210,62 @@ class Cirrocumulus
179
210
  @im.send!("<presence to='#{JABBER_CONFERENCE}/#{@jid}' />")
180
211
  end
181
212
  end
182
-
183
- def flatten_message_content(message)
184
- if !message.content.is_a?(Array)
185
- message.content = [message.content]
186
- else
187
- while message.content.is_a?(Array) && message.content.size == 1 && message.content.first.is_a?(Array)
188
- message.content = message.content.first
213
+
214
+ def process_incoming_message(agent, kb, message, s, sniff)
215
+ return if !message.x('jabber:x:delay').nil?
216
+
217
+ begin
218
+ xml = Hash.from_xml(message.body)['fipa_message']
219
+
220
+ ontology = xml['ontology']
221
+ sender = message.from.resource
222
+ receiver = xml['receiver']
223
+ supported_ontology = agent.handles_ontology?(ontology)
224
+
225
+ if (supported_ontology && (receiver == @jid || receiver.blank?)) || receiver == @jid || sniff
226
+ content_raw = xml['content']
227
+ content = s.parse_string(content_raw)
228
+ msg = Cirrocumulus::Message.new(sender, xml['act'], content)
229
+ msg.receiver = receiver
230
+ msg.ontology = ontology
231
+ msg.reply_with = xml['reply_with']
232
+ msg.in_reply_to = xml['in_reply_to']
233
+ msg.conversation_id = xml['conversation_id']
234
+
235
+ flatten_message_content(msg)
236
+ Log4r::Logger['cirrocumulus'].debug(msg.inspect)
237
+ agent.handle_message(msg)
238
+ else # ignore message
239
+ #Log4r::Logger['cirrocumulus'].debug("unhandled ontology: %s" % [ontology])
189
240
  end
241
+ rescue Exception => e # exception while parsing; possibly it is not XML (humans speaking!)
242
+ Log4r::Logger['cirrocumulus'].error "%s\n%s" % [e.to_s, e.backtrace.to_s]
243
+ end
244
+ end
245
+
246
+ # Processes send queue and sends all pending messages
247
+ def process_send_queue()
248
+ while !@send_queue.empty? do
249
+ message = @send_queue.pop
250
+ actual_send(message)
190
251
  end
191
252
  end
253
+
254
+ # Sends message to Jabber conference
255
+ def actual_send(message)
256
+ msg = "<fipa-message ontology=\"#{message.ontology}\""
257
+ msg += " receiver=\"#{message.receiver}\"" if message.receiver
258
+ msg += " act=\"#{message.act}\""
259
+ msg += " reply-with=\"#{message.reply_with}\"" if message.reply_with
260
+ msg += " in-reply-to=\"#{message.in_reply_to}\"" if message.in_reply_to
261
+ msg += " conversation-id=\"#{message.conversation_id}\"" if message.conversation_id
262
+ message_content = message.content if message.content.is_a?(String)
263
+ message_content = Sexpistol.new.to_sexp(message.content) if message.content.is_a? Array
264
+ msg += "><content>#{message_content}</content></fipa-message>"
265
+ @im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')}</body></message>")
266
+ true
267
+ rescue
268
+ false
269
+ end
270
+
192
271
  end