cirrocumulus 0.4.6 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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