diff options
author | Dr Scofield | 2008-10-20 17:31:54 +0000 |
---|---|---|
committer | Dr Scofield | 2008-10-20 17:31:54 +0000 |
commit | 72a388a7b6dfba8f93ffc5c5c45db4c44bb46480 (patch) | |
tree | db9ab06f6820806d8c2b3fc32029383971e955b7 /OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs | |
parent | Mantis #2438 (diff) | |
download | opensim-SC-72a388a7b6dfba8f93ffc5c5c45db4c44bb46480.zip opensim-SC-72a388a7b6dfba8f93ffc5c5c45db4c44bb46480.tar.gz opensim-SC-72a388a7b6dfba8f93ffc5c5c45db4c44bb46480.tar.bz2 opensim-SC-72a388a7b6dfba8f93ffc5c5c45db4c44bb46480.tar.xz |
cleaning up IRCBridgeModule to allow for configuration from in-world,
chat relaying via private channels, and old IRCBridgeModule
behaviour. also cleaning up IRCBridgeModule's OpenSim.ini
configuration variable names (still supporting "old" variable
names). refactored IRCChatModule into IRCConnector and incorporating
watchdog from IRCBridgeModule into IRCConnector.
enabling ChatModule to be used as a super-class and utilizing it in
ConciergeModule.
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs')
-rw-r--r-- | OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs | 841 |
1 files changed, 104 insertions, 737 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs index e992862..05718d8 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs | |||
@@ -48,10 +48,8 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
48 | 48 | ||
49 | private const int DEBUG_CHANNEL = 2147483647; | 49 | private const int DEBUG_CHANNEL = 2147483647; |
50 | 50 | ||
51 | private string m_defaultzone = null; | ||
52 | 51 | ||
53 | private IRCChatModule m_irc = null; | 52 | private IRCConnector m_irc = null; |
54 | private Thread m_irc_connector = null; | ||
55 | 53 | ||
56 | private string m_last_leaving_user = null; | 54 | private string m_last_leaving_user = null; |
57 | private string m_last_new_user = null; | 55 | private string m_last_new_user = null; |
@@ -61,6 +59,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
61 | internal object m_syncLogout = new object(); | 59 | internal object m_syncLogout = new object(); |
62 | 60 | ||
63 | private IConfig m_config; | 61 | private IConfig m_config; |
62 | private string m_defaultzone = null; | ||
63 | private bool m_commandsEnabled = false; | ||
64 | private int m_commandChannel = -1; | ||
65 | private bool m_relayPrivateChannels = false; | ||
66 | private int m_relayChannelOut = -1; | ||
67 | private bool m_clientReporting = true; | ||
64 | 68 | ||
65 | #region IRegionModule Members | 69 | #region IRegionModule Members |
66 | 70 | ||
@@ -86,6 +90,15 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
86 | return; | 90 | return; |
87 | } | 91 | } |
88 | 92 | ||
93 | m_commandsEnabled = m_config.GetBoolean("commands_enabled", m_commandsEnabled); | ||
94 | m_commandChannel = m_config.GetInt("commandchannel", m_commandChannel); // compat | ||
95 | m_commandChannel = m_config.GetInt("command_channel", m_commandChannel); | ||
96 | |||
97 | m_relayPrivateChannels = m_config.GetBoolean("relay_private_channels", m_relayPrivateChannels); | ||
98 | m_relayChannelOut = m_config.GetInt("relay_private_channel_out", m_relayChannelOut); | ||
99 | |||
100 | m_clientReporting = m_config.GetBoolean("report_clients", m_clientReporting); | ||
101 | |||
89 | lock (m_syncInit) | 102 | lock (m_syncInit) |
90 | { | 103 | { |
91 | 104 | ||
@@ -110,43 +123,21 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
110 | // setup IRC Relay | 123 | // setup IRC Relay |
111 | if (m_irc == null) | 124 | if (m_irc == null) |
112 | { | 125 | { |
113 | m_irc = new IRCChatModule(config); | 126 | m_irc = new IRCConnector(config); |
114 | } | 127 | } |
115 | 128 | m_irc.AddScene(scene); | |
116 | if (m_irc_connector == null) | 129 | |
117 | { | 130 | m_log.InfoFormat("[IRC] initialized for {0}, nick: {1}, commands {2}, private channels {3}", |
118 | m_irc_connector = new Thread(IRCConnectRun); | 131 | scene.RegionInfo.RegionName, m_defaultzone, |
119 | m_irc_connector.Name = "IRCConnectorThread"; | 132 | m_commandsEnabled ? "enabled" : "not enabled", |
120 | m_irc_connector.IsBackground = true; | 133 | m_relayPrivateChannels ? "relayed" : "not relayed"); |
121 | } | ||
122 | m_log.InfoFormat("[IRC] initialized for {0}, nick: {1} ", scene.RegionInfo.RegionName, | ||
123 | m_defaultzone); | ||
124 | } | 134 | } |
125 | } | 135 | } |
126 | 136 | ||
127 | public void PostInitialise() | 137 | public void PostInitialise() |
128 | { | 138 | { |
129 | if (null == m_irc || !m_irc.Enabled) return; | 139 | if (null == m_irc || !m_irc.Enabled) return; |
130 | 140 | m_irc.Start(); | |
131 | try | ||
132 | { | ||
133 | //m_irc.Connect(m_scenes); | ||
134 | if (m_irc_connector == null) | ||
135 | { | ||
136 | m_irc_connector = new Thread(IRCConnectRun); | ||
137 | m_irc_connector.Name = "IRCConnectorThread"; | ||
138 | m_irc_connector.IsBackground = true; | ||
139 | } | ||
140 | |||
141 | if (!m_irc_connector.IsAlive) | ||
142 | { | ||
143 | m_irc_connector.Start(); | ||
144 | ThreadTracker.Add(m_irc_connector); | ||
145 | } | ||
146 | } | ||
147 | catch (Exception) | ||
148 | { | ||
149 | } | ||
150 | } | 141 | } |
151 | 142 | ||
152 | public void Close() | 143 | public void Close() |
@@ -172,14 +163,21 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
172 | 163 | ||
173 | #region ISimChat Members | 164 | #region ISimChat Members |
174 | 165 | ||
175 | public void OnSimChat(Object sender, OSChatMessage e) | 166 | public void OnSimChat(Object sender, OSChatMessage c) |
176 | { | 167 | { |
177 | m_log.DebugFormat("[IRC] heard on channel {0}: {1}", e.Channel.ToString(), e.Message); | 168 | // early return if nothing to forward |
169 | if (c.Message.Length == 0) return; | ||
170 | |||
171 | // early return if this comes from the IRC forwarder | ||
172 | if (m_irc.Equals(sender)) return; | ||
173 | |||
174 | m_log.DebugFormat("[IRC] heard on channel {0}: {1}", c.Channel, c.Message); | ||
178 | 175 | ||
179 | // We only want to relay stuff on channel 0 | 176 | // check for commands coming from avatars or in-world |
180 | if (e.Channel == m_irc.m_commandChannel) | 177 | // object (if commands are enabled) |
178 | if (m_commandsEnabled && c.Channel == m_commandChannel) | ||
181 | { | 179 | { |
182 | string[] messages = e.Message.Split(' '); | 180 | string[] messages = c.Message.Split(' '); |
183 | string command = messages[0].ToLower(); | 181 | string command = messages[0].ToLower(); |
184 | 182 | ||
185 | try | 183 | try |
@@ -187,146 +185,135 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
187 | switch (command) | 185 | switch (command) |
188 | { | 186 | { |
189 | case "channel": | 187 | case "channel": |
190 | m_irc.m_channel = messages[1]; | 188 | m_irc.IrcChannel = messages[1]; |
191 | break; | 189 | break; |
192 | case "close": | 190 | case "close": |
193 | m_irc.Close(); | 191 | m_irc.Close(); |
194 | break; | 192 | break; |
195 | case "connect": | 193 | case "connect": |
196 | m_irc.Connect(m_scenes); | 194 | m_irc.Connect(); |
197 | break; | 195 | break; |
198 | case "nick": | 196 | case "nick": |
199 | m_irc.m_nick = messages[1]; | 197 | m_irc.Nick = messages[1]; |
200 | break; | 198 | break; |
201 | case "port": | 199 | case "port": |
202 | m_irc.m_port = Convert.ToUInt32(messages[1]); | 200 | m_irc.Port = Convert.ToUInt32(messages[1]); |
203 | break; | 201 | break; |
204 | case "reconnect": | 202 | case "reconnect": |
205 | m_irc.Reconnect(); | 203 | m_irc.Reconnect(); |
206 | break; | 204 | break; |
207 | case "server": | 205 | case "server": |
208 | m_irc.m_server = messages[1]; | 206 | m_irc.Server = messages[1]; |
209 | break; | 207 | break; |
210 | case "verbosity": | 208 | case "client-reporting": |
211 | m_irc.m_verbosity = Convert.ToInt32(messages[1]); | 209 | m_irc.ClientReporting = Convert.ToBoolean(messages[1]); |
210 | |||
212 | break; | 211 | break; |
213 | case "in-channel": | 212 | case "in-channel": |
214 | m_irc.m_messageOutChannel = Convert.ToInt32(messages[1]); | 213 | m_irc.RelayChannel = Convert.ToInt32(messages[1]); |
215 | break; | 214 | break; |
216 | case "out-channel": | 215 | case "out-channel": |
217 | m_irc.m_messageInChannel = Convert.ToInt32(messages[1]); | 216 | m_relayChannelOut = Convert.ToInt32(messages[1]); |
218 | break; | 217 | break; |
219 | 218 | ||
220 | default: | 219 | default: |
221 | m_irc.Send(e.Message); | 220 | m_irc.Send(c.Message); |
222 | break; | 221 | break; |
223 | } | 222 | } |
224 | } | 223 | } |
225 | catch | 224 | catch(Exception ex) |
226 | { } | 225 | { |
226 | m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex); | ||
227 | } | ||
227 | } | 228 | } |
228 | 229 | ||
229 | if (e.Channel != m_irc.m_messageOutChannel) return; | 230 | // drop all messages coming in on a private channel, |
230 | if (e.Message.Length == 0) return; | 231 | // except if we are relaying private channels, in which |
231 | 232 | // case we drop if the private channel is not the | |
232 | // not interested in our own babblings | 233 | // configured m_relayChannelOut |
233 | if (m_irc.Equals(sender)) return; | 234 | if (m_relayPrivateChannels) |
235 | { | ||
236 | if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL && c.Channel != m_relayChannelOut) | ||
237 | { | ||
238 | m_log.DebugFormat("[IRC] dropping message {0} on channel {1}", c, c.Channel); | ||
239 | return; | ||
240 | } | ||
241 | } | ||
242 | else if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) | ||
243 | { | ||
244 | m_log.DebugFormat("[IRC] dropping message {0} on channel {1}", c, c.Channel); | ||
245 | return; | ||
246 | } | ||
234 | 247 | ||
235 | ScenePresence avatar = null; | 248 | ScenePresence avatar = null; |
236 | Scene scene = (Scene)e.Scene; | 249 | Scene scene = (Scene)c.Scene; |
237 | 250 | ||
238 | if (scene == null) | 251 | if (scene == null) |
239 | scene = m_scenes[0]; | 252 | scene = m_scenes[0]; |
240 | 253 | ||
241 | // Filled in since it's easier than rewriting right now. | 254 | string fromName = c.From; |
242 | string fromName = e.From; | ||
243 | 255 | ||
244 | if (e.Sender != null) | 256 | if (c.Sender != null) |
245 | { | 257 | { |
246 | avatar = scene.GetScenePresence(e.Sender.AgentId); | 258 | avatar = scene.GetScenePresence(c.Sender.AgentId); |
259 | if (avatar != null) fromName = avatar.Name; | ||
247 | } | 260 | } |
248 | 261 | ||
249 | if (avatar != null) | 262 | if (!m_irc.Connected) |
250 | { | 263 | { |
251 | fromName = avatar.Name; | 264 | m_log.WarnFormat("[IRC] IRCConnector not connected: dropping message from {0}", fromName); |
265 | return; | ||
252 | } | 266 | } |
253 | 267 | ||
254 | // Try to reconnect to server if not connected | 268 | if (null != avatar) |
255 | if (m_irc.Enabled && !m_irc.Connected) | ||
256 | { | 269 | { |
257 | // In a non-blocking way. Eventually the connector will get it started | 270 | string msg = c.Message; |
258 | try | 271 | if (msg.StartsWith("/me ")) |
272 | msg = String.Format("{0} {1}", fromName, c.Message.Substring(4)); | ||
273 | |||
274 | m_irc.PrivMsg(fromName, scene.RegionInfo.RegionName, msg); | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | //Message came from an object | ||
279 | char[] splits = { ',' }; | ||
280 | string[] tokens = c.Message.Split(splits,3); // This is certainly wrong | ||
281 | if (tokens.Length == 3) | ||
259 | { | 282 | { |
260 | if (m_irc_connector == null) | 283 | if (tokens[0] == m_irc.m_accessPassword) // This is my really simple check |
261 | { | 284 | { |
262 | m_irc_connector = new Thread(IRCConnectRun); | 285 | m_log.DebugFormat("[IRC] message from object {0}, {1}", tokens[0], tokens[1]); |
263 | m_irc_connector.Name = "IRCConnectorThread"; | 286 | m_irc.PrivMsg(tokens[1], scene.RegionInfo.RegionName, tokens[2]); |
264 | m_irc_connector.IsBackground = true; | ||
265 | } | 287 | } |
266 | 288 | else | |
267 | if (!m_irc_connector.IsAlive) | ||
268 | { | 289 | { |
269 | m_irc_connector.Start(); | 290 | m_log.WarnFormat("[IRC] prim security key mismatch <{0}> not <{1}>", tokens[0], m_irc.m_accessPassword); |
270 | ThreadTracker.Add(m_irc_connector); | ||
271 | } | 291 | } |
272 | } | 292 | } |
273 | catch (Exception) | ||
274 | { | ||
275 | } | ||
276 | } | 293 | } |
277 | |||
278 | if (e.Message.StartsWith("/me ") && (null != avatar)) | ||
279 | e.Message = String.Format("{0} {1}", fromName, e.Message.Substring(4)); | ||
280 | |||
281 | // this is to keep objects from talking to IRC | ||
282 | if (m_irc.Connected && (avatar != null)) | ||
283 | m_irc.PrivMsg(fromName, scene.RegionInfo.RegionName, e.Message); | ||
284 | |||
285 | // Handle messages from objects | ||
286 | if (m_irc.Connected && (null == avatar)) | ||
287 | { | ||
288 | //Message came from an object | ||
289 | char[] splits = { ',' }; | ||
290 | string[] tokens = e.Message.Split(splits,3); // This is certainly wrong | ||
291 | if (tokens.Length == 3) | ||
292 | { | ||
293 | if (tokens[0] == m_irc.m_accessPassword) // This is my really simple check | ||
294 | { | ||
295 | m_log.DebugFormat("[IRC] message from object {0}, {1}", tokens[0], tokens[1]); | ||
296 | m_irc.PrivMsg(tokens[1], scene.RegionInfo.RegionName, tokens[2]); | ||
297 | } | ||
298 | else | ||
299 | { | ||
300 | m_log.WarnFormat("[IRC] prim security key mismatch <{0}> not <{1}>", tokens[0], m_irc.m_accessPassword); | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | } | 294 | } |
305 | |||
306 | #endregion | 295 | #endregion |
307 | 296 | ||
308 | public void OnNewClient(IClientAPI client) | 297 | public void OnNewClient(IClientAPI client) |
309 | { | 298 | { |
310 | try | 299 | try |
311 | { | 300 | { |
312 | // client.OnChatFromViewer += OnSimChat; | ||
313 | client.OnLogout += OnClientLoggedOut; | 301 | client.OnLogout += OnClientLoggedOut; |
314 | client.OnConnectionClosed += OnClientLoggedOut; | 302 | client.OnConnectionClosed += OnClientLoggedOut; |
315 | 303 | ||
316 | if (client.Name != m_last_new_user) | 304 | if (client.Name != m_last_new_user) |
317 | { | 305 | { |
318 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_irc.m_verbosity >= 1)) | 306 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_clientReporting)) |
319 | { | 307 | { |
320 | m_log.DebugFormat("[IRC] {0} logging on", client.Name); | 308 | m_log.DebugFormat("[IRC] {0} logging on", client.Name); |
321 | m_irc.PrivMsg(m_irc.Nick, "Sim", | 309 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} logging on", client.Name)); |
322 | String.Format("notices {0} logging on", client.Name)); | ||
323 | } | 310 | } |
324 | m_last_new_user = client.Name; | 311 | m_last_new_user = client.Name; |
325 | } | 312 | } |
326 | } | 313 | } |
327 | catch (Exception ex) | 314 | catch (Exception ex) |
328 | { | 315 | { |
329 | m_log.Error("[IRC]: NewClient exception trap:" + ex.ToString()); | 316 | m_log.Error("[IRC]: OnNewClient exception trap:" + ex.ToString()); |
330 | } | 317 | } |
331 | } | 318 | } |
332 | 319 | ||
@@ -334,7 +321,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
334 | { | 321 | { |
335 | try | 322 | try |
336 | { | 323 | { |
337 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_irc.m_verbosity >= 2)) | 324 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_clientReporting)) |
338 | { | 325 | { |
339 | string regionName = presence.Scene.RegionInfo.RegionName; | 326 | string regionName = presence.Scene.RegionInfo.RegionName; |
340 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); | 327 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); |
@@ -351,7 +338,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
351 | { | 338 | { |
352 | try | 339 | try |
353 | { | 340 | { |
354 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_irc.m_verbosity >= 2)) | 341 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_clientReporting)) |
355 | { | 342 | { |
356 | string regionName = presence.Scene.RegionInfo.RegionName; | 343 | string regionName = presence.Scene.RegionInfo.RegionName; |
357 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); | 344 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); |
@@ -371,16 +358,14 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
371 | { | 358 | { |
372 | try | 359 | try |
373 | { | 360 | { |
374 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_irc.m_verbosity >= 1)) | 361 | if ((m_irc.Enabled) && (m_irc.Connected) && (m_clientReporting)) |
375 | { | 362 | { |
376 | // handles simple case. May not work for hundred connecting in per second. | 363 | // handles simple case. May not work for |
377 | // and the NewClients calles getting interleved | 364 | // hundred connecting in per second. and |
378 | // but filters out multiple reports | 365 | // OnNewClients calle getting interleaved but |
366 | // filters out multiple reports | ||
379 | if (client.Name != m_last_leaving_user) | 367 | if (client.Name != m_last_leaving_user) |
380 | { | 368 | { |
381 | // Console.WriteLine("Avatar was seen logging out."); | ||
382 | // Console.ReadLine(); | ||
383 | // Console.WriteLine(); | ||
384 | m_last_leaving_user = client.Name; | 369 | m_last_leaving_user = client.Name; |
385 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} logging out", client.Name)); | 370 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} logging out", client.Name)); |
386 | m_log.InfoFormat("[IRC]: {0} logging out", client.Name); | 371 | m_log.InfoFormat("[IRC]: {0} logging out", client.Name); |
@@ -396,623 +381,5 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
396 | } | 381 | } |
397 | } | 382 | } |
398 | } | 383 | } |
399 | |||
400 | // if IRC is enabled then just keep trying using a monitor thread | ||
401 | public void IRCConnectRun() | ||
402 | { | ||
403 | while (m_irc.Enabled) | ||
404 | { | ||
405 | if (!m_irc.Connected) | ||
406 | { | ||
407 | m_irc.Connect(m_scenes); | ||
408 | } | ||
409 | Thread.Sleep(15000); | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | |||
414 | internal class IRCChatModule | ||
415 | { | ||
416 | #region ErrorReplies enum | ||
417 | |||
418 | public enum ErrorReplies | ||
419 | { | ||
420 | NotRegistered = 451, // ":You have not registered" | ||
421 | NicknameInUse = 433 // "<nick> :Nickname is already in use" | ||
422 | } | ||
423 | |||
424 | #endregion | ||
425 | |||
426 | #region Replies enum | ||
427 | |||
428 | public enum Replies | ||
429 | { | ||
430 | MotdStart = 375, // ":- <server> Message of the day - " | ||
431 | Motd = 372, // ":- <text>" | ||
432 | EndOfMotd = 376 // ":End of /MOTD command" | ||
433 | } | ||
434 | |||
435 | #endregion | ||
436 | |||
437 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
438 | private Thread listener; | ||
439 | |||
440 | private string m_basenick = null; | ||
441 | public string m_channel = null; | ||
442 | private bool m_nrnick = false; | ||
443 | private bool m_connected = false; | ||
444 | private bool m_enabled = false; | ||
445 | public int m_commandChannel = -1; | ||
446 | |||
447 | public int m_verbosity = 1; | ||
448 | public int m_messageOutChannel = 0; // Will be the message channel we listen to | ||
449 | public int m_messageInChannel = 0; // Will be the message channel where we forward msgs | ||
450 | public string m_accessPassword = "badkitty"; | ||
451 | public bool m_useWorldComm = false; // true if we want chat to be localized, false if we want to broadcast to the entire region | ||
452 | |||
453 | public List<Scene> m_last_scenes = null; | ||
454 | public string m_nick = null; | ||
455 | public uint m_port = 6668; | ||
456 | private string m_privmsgformat = "PRIVMSG {0} :<{1} in {2}>: {3}"; | ||
457 | private StreamReader m_reader; | ||
458 | private List<Scene> m_scenes = null; | ||
459 | public string m_server = null; | ||
460 | |||
461 | private NetworkStream m_stream = null; | ||
462 | internal object m_syncConnect = new object(); | ||
463 | private TcpClient m_tcp; | ||
464 | private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot"; | ||
465 | private StreamWriter m_writer; | ||
466 | |||
467 | private Thread pingSender; | ||
468 | |||
469 | public IRCChatModule(IConfigSource config) | ||
470 | { | ||
471 | m_nick = "OSimBot" + Util.RandomClass.Next(1, 99); | ||
472 | m_tcp = null; | ||
473 | m_writer = null; | ||
474 | m_reader = null; | ||
475 | |||
476 | // configuration in OpenSim.ini | ||
477 | // [IRC] | ||
478 | // server = chat.freenode.net | ||
479 | // nick = OSimBot_mysim | ||
480 | // nicknum = true | ||
481 | // ;nicknum set to true appends a 2 digit random number to the nick | ||
482 | // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot | ||
483 | // ; username is the IRC command line sent | ||
484 | // ; USER <irc_user> <visible=8,invisible=0> * : <IRC_realname> | ||
485 | // channel = #opensim-regions | ||
486 | // port = 6667 | ||
487 | // ;MSGformat fields : 0=botnick, 1=user, 2=region, 3=message | ||
488 | // ;for <bot>:<user in region> :<message> | ||
489 | // ;msgformat = "PRIVMSG {0} :<{1} in {2}>: {3}" | ||
490 | // ;for <bot>:<message> - <user of region> : | ||
491 | // ;msgformat = "PRIVMSG {0} : {3} - {1} of {2}" | ||
492 | // ;for <bot>:<message> - from <user> : | ||
493 | // ;msgformat = "PRIVMSG {0} : {3} - from {1}" | ||
494 | // Traps I/O disconnects so it does not crash the sim | ||
495 | // Trys to reconnect if disconnected and someone says something | ||
496 | // Tells IRC server "QUIT" when doing a close (just to be nice) | ||
497 | // Default port back to 6667 | ||
498 | |||
499 | try | ||
500 | { | ||
501 | m_server = config.Configs["IRC"].GetString("server"); | ||
502 | m_nick = config.Configs["IRC"].GetString("nick"); | ||
503 | m_basenick = m_nick; | ||
504 | m_nrnick = config.Configs["IRC"].GetBoolean("nicknum", true); | ||
505 | m_channel = config.Configs["IRC"].GetString("channel"); | ||
506 | m_port = (uint)config.Configs["IRC"].GetInt("port", (int)m_port); | ||
507 | m_user = config.Configs["IRC"].GetString("username", m_user); | ||
508 | m_privmsgformat = config.Configs["IRC"].GetString("msgformat", m_privmsgformat); | ||
509 | m_commandChannel = config.Configs["IRC"].GetInt("commandchannel", m_commandChannel); | ||
510 | |||
511 | m_verbosity = config.Configs["IRC"].GetInt("verbosity", m_verbosity); | ||
512 | m_messageOutChannel = config.Configs["IRC"].GetInt("outchannel", m_messageOutChannel); | ||
513 | m_messageInChannel = config.Configs["IRC"].GetInt("inchannel", m_messageInChannel); | ||
514 | m_accessPassword = config.Configs["IRC"].GetString("access_password",m_accessPassword); | ||
515 | m_useWorldComm = config.Configs["IRC"].GetBoolean("useworldcomm", m_useWorldComm); | ||
516 | |||
517 | if (m_server != null && m_nick != null && m_channel != null) | ||
518 | { | ||
519 | if (m_nrnick == true) | ||
520 | { | ||
521 | m_nick = m_nick + Util.RandomClass.Next(1, 99); | ||
522 | } | ||
523 | m_enabled = true; | ||
524 | } | ||
525 | } | ||
526 | catch (Exception ex) | ||
527 | { | ||
528 | m_log.Error("[IRC]: Incomplete IRC configuration, skipping IRC bridge configuration"); | ||
529 | m_log.DebugFormat("[IRC] Incomplete IRC configuration: {0}", ex.ToString()); | ||
530 | } | ||
531 | } | ||
532 | |||
533 | public bool Enabled | ||
534 | { | ||
535 | get { return m_enabled; } | ||
536 | } | ||
537 | |||
538 | public bool Connected | ||
539 | { | ||
540 | get { return m_connected; } | ||
541 | } | ||
542 | |||
543 | public string Nick | ||
544 | { | ||
545 | get { return m_nick; } | ||
546 | } | ||
547 | |||
548 | public bool Connect(List<Scene> scenes) | ||
549 | { | ||
550 | lock (m_syncConnect) | ||
551 | { | ||
552 | try | ||
553 | { | ||
554 | if (m_connected) return true; | ||
555 | |||
556 | m_scenes = scenes; | ||
557 | if (m_last_scenes == null) | ||
558 | { | ||
559 | m_last_scenes = scenes; | ||
560 | } | ||
561 | |||
562 | m_tcp = new TcpClient(m_server, (int)m_port); | ||
563 | m_stream = m_tcp.GetStream(); | ||
564 | m_reader = new StreamReader(m_stream); | ||
565 | m_writer = new StreamWriter(m_stream); | ||
566 | |||
567 | m_log.DebugFormat("[IRC]: Connected to {0}:{1}", m_server, m_port); | ||
568 | |||
569 | pingSender = new Thread(new ThreadStart(PingRun)); | ||
570 | pingSender.Name = "PingSenderThread"; | ||
571 | pingSender.IsBackground = true; | ||
572 | pingSender.Start(); | ||
573 | ThreadTracker.Add(pingSender); | ||
574 | |||
575 | listener = new Thread(new ThreadStart(ListenerRun)); | ||
576 | listener.Name = "IRCChatModuleListenerThread"; | ||
577 | listener.IsBackground = true; | ||
578 | listener.Start(); | ||
579 | ThreadTracker.Add(listener); | ||
580 | |||
581 | m_writer.WriteLine(m_user); | ||
582 | m_writer.Flush(); | ||
583 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
584 | m_writer.Flush(); | ||
585 | m_writer.WriteLine(String.Format("JOIN {0}", m_channel)); | ||
586 | m_writer.Flush(); | ||
587 | m_log.Info("[IRC]: Connection fully established"); | ||
588 | m_connected = true; | ||
589 | } | ||
590 | catch (Exception e) | ||
591 | { | ||
592 | m_log.ErrorFormat("[IRC] cannot connect to {0}:{1}: {2}", | ||
593 | m_server, m_port, e.Message); | ||
594 | } | ||
595 | m_log.Debug("[IRC] Connected"); | ||
596 | return m_connected; | ||
597 | } | ||
598 | } | ||
599 | |||
600 | public void Reconnect() | ||
601 | { | ||
602 | m_connected = false; | ||
603 | try | ||
604 | { | ||
605 | listener.Abort(); | ||
606 | pingSender.Abort(); | ||
607 | m_writer.Close(); | ||
608 | m_reader.Close(); | ||
609 | m_tcp.Close(); | ||
610 | } | ||
611 | catch (Exception) | ||
612 | { | ||
613 | } | ||
614 | |||
615 | if (m_enabled) | ||
616 | { | ||
617 | Connect(m_last_scenes); | ||
618 | } | ||
619 | } | ||
620 | |||
621 | public void PrivMsg(string from, string region, string msg) | ||
622 | { | ||
623 | m_log.DebugFormat("[IRC] Sending message to IRC from {0}: {1}", from, msg); | ||
624 | |||
625 | // One message to the IRC server | ||
626 | try | ||
627 | { | ||
628 | m_writer.WriteLine(m_privmsgformat, m_channel, from, region, msg); | ||
629 | m_writer.Flush(); | ||
630 | m_log.InfoFormat("[IRC]: PrivMsg {0} in {1}: {2}", from, region, msg); | ||
631 | } | ||
632 | catch (IOException) | ||
633 | { | ||
634 | m_log.Error("[IRC]: Disconnected from IRC server.(PrivMsg)"); | ||
635 | Reconnect(); | ||
636 | } | ||
637 | catch (Exception ex) | ||
638 | { | ||
639 | m_log.ErrorFormat("[IRC]: PrivMsg exception trap: {0}", ex.ToString()); | ||
640 | } | ||
641 | } | ||
642 | |||
643 | public void Send(string msg) | ||
644 | { | ||
645 | try | ||
646 | { | ||
647 | m_writer.WriteLine(msg); | ||
648 | m_writer.Flush(); | ||
649 | m_log.Info("IRC: Sent command string: " + msg); | ||
650 | } | ||
651 | catch (IOException) | ||
652 | { | ||
653 | m_log.Error("[IRC]: Disconnected from IRC server.(PrivMsg)"); | ||
654 | Reconnect(); | ||
655 | } | ||
656 | catch (Exception ex) | ||
657 | { | ||
658 | m_log.ErrorFormat("[IRC]: PrivMsg exception trap: {0}", ex.ToString()); | ||
659 | } | ||
660 | |||
661 | } | ||
662 | |||
663 | |||
664 | private Dictionary<string, string> ExtractMsg(string input) | ||
665 | { | ||
666 | //examines IRC commands and extracts any private messages | ||
667 | // which will then be reboadcast in the Sim | ||
668 | |||
669 | m_log.Info("[IRC]: ExtractMsg: " + input); | ||
670 | Dictionary<string, string> result = null; | ||
671 | //string regex = @":(?<nick>\w*)!~(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)"; | ||
672 | string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)"; | ||
673 | Regex RE = new Regex(regex, RegexOptions.Multiline); | ||
674 | MatchCollection matches = RE.Matches(input); | ||
675 | |||
676 | // Get some direct matches $1 $4 is a | ||
677 | if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5)) | ||
678 | { | ||
679 | m_log.Info("[IRC]: Number of matches: " + matches.Count); | ||
680 | if (matches.Count > 0) | ||
681 | { | ||
682 | m_log.Info("[IRC]: Number of groups: " + matches[0].Groups.Count); | ||
683 | } | ||
684 | return null; | ||
685 | } | ||
686 | |||
687 | result = new Dictionary<string, string>(); | ||
688 | result.Add("nick", matches[0].Groups[1].Value); | ||
689 | result.Add("user", matches[0].Groups[2].Value); | ||
690 | result.Add("channel", matches[0].Groups[3].Value); | ||
691 | result.Add("msg", matches[0].Groups[4].Value); | ||
692 | |||
693 | return result; | ||
694 | } | ||
695 | |||
696 | public void PingRun() | ||
697 | { | ||
698 | // IRC keep alive thread | ||
699 | // send PING ever 15 seconds | ||
700 | while (m_enabled) | ||
701 | { | ||
702 | try | ||
703 | { | ||
704 | if (m_connected == true) | ||
705 | { | ||
706 | m_writer.WriteLine(String.Format("PING :{0}", m_server)); | ||
707 | m_writer.Flush(); | ||
708 | Thread.Sleep(15000); | ||
709 | } | ||
710 | } | ||
711 | catch (IOException) | ||
712 | { | ||
713 | if (m_enabled) | ||
714 | { | ||
715 | m_log.Error("[IRC]: Disconnected from IRC server.(PingRun)"); | ||
716 | Reconnect(); | ||
717 | } | ||
718 | } | ||
719 | catch (Exception ex) | ||
720 | { | ||
721 | m_log.ErrorFormat("[IRC]: PingRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
722 | } | ||
723 | } | ||
724 | } | ||
725 | |||
726 | static private Vector3 pos = new Vector3(128, 128, 20); | ||
727 | public void ListenerRun() | ||
728 | { | ||
729 | string inputLine; | ||
730 | |||
731 | while (m_enabled) | ||
732 | { | ||
733 | try | ||
734 | { | ||
735 | while ((m_connected == true) && ((inputLine = m_reader.ReadLine()) != null)) | ||
736 | { | ||
737 | m_log.Info("[IRC]: " + inputLine); | ||
738 | if (inputLine.Contains(m_channel)) | ||
739 | { | ||
740 | Dictionary<string, string> data = ExtractMsg(inputLine); | ||
741 | // Any chat ??? | ||
742 | if (data != null) | ||
743 | { | ||
744 | OSChatMessage c = new OSChatMessage(); | ||
745 | c.Message = data["msg"]; | ||
746 | c.Type = ChatTypeEnum.Say; | ||
747 | c.Channel = m_messageInChannel; | ||
748 | c.Position = pos; | ||
749 | c.From = data["nick"]; | ||
750 | c.Sender = null; | ||
751 | c.SenderUUID = UUID.Zero; | ||
752 | |||
753 | // is message "\001ACTION foo | ||
754 | // bar\001"? -> "/me foo bar" | ||
755 | if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION")) | ||
756 | c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9)); | ||
757 | |||
758 | m_log.DebugFormat("[IRC] ListenerRun from: {0}, {1}", c.From, c.Message); | ||
759 | |||
760 | foreach (Scene scene in m_scenes) | ||
761 | { | ||
762 | if (m_useWorldComm) | ||
763 | { | ||
764 | IWorldComm wComm = scene.RequestModuleInterface<IWorldComm>(); | ||
765 | wComm.DeliverMessage(ChatTypeEnum.Region, m_messageInChannel, c.From, UUID.Zero, c.Message); | ||
766 | } | ||
767 | else | ||
768 | { | ||
769 | c.Scene = scene; | ||
770 | scene.EventManager.TriggerOnChatBroadcast(this, c); | ||
771 | } | ||
772 | } | ||
773 | } | ||
774 | |||
775 | Thread.Sleep(150); | ||
776 | continue; | ||
777 | } | ||
778 | |||
779 | ProcessIRCCommand(inputLine); | ||
780 | Thread.Sleep(150); | ||
781 | } | ||
782 | } | ||
783 | catch (IOException) | ||
784 | { | ||
785 | if (m_enabled) | ||
786 | { | ||
787 | m_log.Error("[IRC]: ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)"); | ||
788 | Reconnect(); | ||
789 | } | ||
790 | } | ||
791 | catch (Exception ex) | ||
792 | { | ||
793 | m_log.ErrorFormat("[IRC]: ListenerRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
794 | } | ||
795 | } | ||
796 | } | ||
797 | |||
798 | public void BroadcastSim(string sender, string format, params string[] args) | ||
799 | { | ||
800 | try | ||
801 | { | ||
802 | OSChatMessage c = new OSChatMessage(); | ||
803 | c.From = sender; | ||
804 | c.Message = String.Format(format, args); | ||
805 | c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say; | ||
806 | c.Channel = m_messageInChannel; | ||
807 | c.Position = new Vector3(128, 128, 20); | ||
808 | c.Sender = null; | ||
809 | c.SenderUUID = UUID.Zero; | ||
810 | |||
811 | m_log.DebugFormat("[IRC] BroadcastSim from {0}: {1}", c.From, c.Message); | ||
812 | |||
813 | foreach (Scene m_scene in m_scenes) | ||
814 | { | ||
815 | c.Scene = m_scene; | ||
816 | // m_scene.EventManager.TriggerOnChatBroadcast(this, c); | ||
817 | // m_scene.EventManager.TriggerOnChatFromWorld(this, c); | ||
818 | IWorldComm wComm = m_scene.RequestModuleInterface<IWorldComm>(); | ||
819 | wComm.DeliverMessage(ChatTypeEnum.Region, m_messageInChannel, sender, UUID.Zero, c.Message); | ||
820 | //IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>(); | ||
821 | //wComm.DeliverMessage(ChatTypeEnum.Region, channelID, m_host.Name, m_host.UUID, text); | ||
822 | |||
823 | } | ||
824 | } | ||
825 | catch (Exception ex) // IRC gate should not crash Sim | ||
826 | { | ||
827 | m_log.ErrorFormat("[IRC]: BroadcastSim Exception Trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
828 | } | ||
829 | } | ||
830 | |||
831 | public void ProcessIRCCommand(string command) | ||
832 | { | ||
833 | m_log.Debug("[IRC]: ProcessIRCCommand:" + command); | ||
834 | |||
835 | string[] commArgs = new string[command.Split(' ').Length]; | ||
836 | string c_server = m_server; | ||
837 | |||
838 | commArgs = command.Split(' '); | ||
839 | if (commArgs[0].Substring(0, 1) == ":") | ||
840 | { | ||
841 | commArgs[0] = commArgs[0].Remove(0, 1); | ||
842 | } | ||
843 | |||
844 | if (commArgs[1] == "002") | ||
845 | { | ||
846 | // fetch the correct servername | ||
847 | // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/... | ||
848 | // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch | ||
849 | |||
850 | c_server = (commArgs[6].Split('['))[0]; | ||
851 | m_server = c_server; | ||
852 | } | ||
853 | |||
854 | if (commArgs[0] == "ERROR") | ||
855 | { | ||
856 | m_log.ErrorFormat("[IRC]: IRC SERVER ERROR: {0}", command); | ||
857 | } | ||
858 | |||
859 | if (commArgs[0] == "PING") | ||
860 | { | ||
861 | string p_reply = ""; | ||
862 | |||
863 | for (int i = 1; i < commArgs.Length; i++) | ||
864 | { | ||
865 | p_reply += commArgs[i] + " "; | ||
866 | } | ||
867 | |||
868 | m_writer.WriteLine(String.Format("PONG {0}", p_reply)); | ||
869 | m_writer.Flush(); | ||
870 | } | ||
871 | else if (commArgs[0] == c_server) | ||
872 | { | ||
873 | // server message | ||
874 | try | ||
875 | { | ||
876 | Int32 commandCode = Int32.Parse(commArgs[1]); | ||
877 | switch (commandCode) | ||
878 | { | ||
879 | case (int)ErrorReplies.NicknameInUse: | ||
880 | // Gen a new name | ||
881 | m_nick = m_basenick + Util.RandomClass.Next(1, 99); | ||
882 | m_log.ErrorFormat("[IRC]: IRC SERVER reports NicknameInUse, trying {0}", m_nick); | ||
883 | // Retry | ||
884 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
885 | m_writer.Flush(); | ||
886 | m_writer.WriteLine(String.Format("JOIN {0}", m_channel)); | ||
887 | m_writer.Flush(); | ||
888 | break; | ||
889 | case (int)ErrorReplies.NotRegistered: | ||
890 | break; | ||
891 | case (int)Replies.EndOfMotd: | ||
892 | break; | ||
893 | } | ||
894 | } | ||
895 | catch (Exception) | ||
896 | { | ||
897 | } | ||
898 | } | ||
899 | else | ||
900 | { | ||
901 | // Normal message | ||
902 | string commAct = commArgs[1]; | ||
903 | switch (commAct) | ||
904 | { | ||
905 | case "JOIN": | ||
906 | eventIrcJoin(commArgs); | ||
907 | break; | ||
908 | case "PART": | ||
909 | eventIrcPart(commArgs); | ||
910 | break; | ||
911 | case "MODE": | ||
912 | eventIrcMode(commArgs); | ||
913 | break; | ||
914 | case "NICK": | ||
915 | eventIrcNickChange(commArgs); | ||
916 | break; | ||
917 | case "KICK": | ||
918 | eventIrcKick(commArgs); | ||
919 | break; | ||
920 | case "QUIT": | ||
921 | eventIrcQuit(commArgs); | ||
922 | break; | ||
923 | case "PONG": | ||
924 | break; // that's nice | ||
925 | } | ||
926 | } | ||
927 | } | ||
928 | |||
929 | public void eventIrcJoin(string[] commArgs) | ||
930 | { | ||
931 | string IrcChannel = commArgs[2]; | ||
932 | if (IrcChannel.StartsWith(":")) | ||
933 | IrcChannel = IrcChannel.Substring(1); | ||
934 | string IrcUser = commArgs[0].Split('!')[0]; | ||
935 | if (m_verbosity >= 1) | ||
936 | BroadcastSim(IrcUser, "/me joins {0}", IrcChannel); | ||
937 | } | ||
938 | |||
939 | public void eventIrcPart(string[] commArgs) | ||
940 | { | ||
941 | string IrcChannel = commArgs[2]; | ||
942 | string IrcUser = commArgs[0].Split('!')[0]; | ||
943 | if (m_verbosity >= 2) | ||
944 | BroadcastSim(IrcUser, "/me parts {0}", IrcChannel); | ||
945 | } | ||
946 | |||
947 | public void eventIrcMode(string[] commArgs) | ||
948 | { | ||
949 | string UserMode = ""; | ||
950 | for (int i = 3; i < commArgs.Length; i++) | ||
951 | { | ||
952 | UserMode += commArgs[i] + " "; | ||
953 | } | ||
954 | |||
955 | if (UserMode.Substring(0, 1) == ":") | ||
956 | { | ||
957 | UserMode = UserMode.Remove(0, 1); | ||
958 | } | ||
959 | } | ||
960 | |||
961 | public void eventIrcNickChange(string[] commArgs) | ||
962 | { | ||
963 | string UserOldNick = commArgs[0].Split('!')[0]; | ||
964 | string UserNewNick = commArgs[2].Remove(0, 1); | ||
965 | if (m_verbosity >= 2) | ||
966 | BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick); | ||
967 | } | ||
968 | |||
969 | public void eventIrcKick(string[] commArgs) | ||
970 | { | ||
971 | string UserKicker = commArgs[0].Split('!')[0]; | ||
972 | string UserKicked = commArgs[3]; | ||
973 | string IrcChannel = commArgs[2]; | ||
974 | string KickMessage = ""; | ||
975 | for (int i = 4; i < commArgs.Length; i++) | ||
976 | { | ||
977 | KickMessage += commArgs[i] + " "; | ||
978 | } | ||
979 | if (m_verbosity >= 1) | ||
980 | BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage); | ||
981 | if (UserKicked == m_nick) | ||
982 | { | ||
983 | BroadcastSim(m_nick, "Hey, that was me!!!"); | ||
984 | } | ||
985 | } | ||
986 | |||
987 | public void eventIrcQuit(string[] commArgs) | ||
988 | { | ||
989 | string IrcUser = commArgs[0].Split('!')[0]; | ||
990 | string QuitMessage = ""; | ||
991 | |||
992 | for (int i = 2; i < commArgs.Length; i++) | ||
993 | { | ||
994 | QuitMessage += commArgs[i] + " "; | ||
995 | } | ||
996 | if (m_verbosity >= 1) | ||
997 | BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage); | ||
998 | } | ||
999 | |||
1000 | public void Close() | ||
1001 | { | ||
1002 | m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing", | ||
1003 | m_nick, m_channel, m_server)); | ||
1004 | m_writer.Flush(); | ||
1005 | |||
1006 | m_connected = false; | ||
1007 | m_enabled = false; | ||
1008 | |||
1009 | //listener.Abort(); | ||
1010 | //pingSender.Abort(); | ||
1011 | |||
1012 | m_writer.Close(); | ||
1013 | m_reader.Close(); | ||
1014 | m_stream.Close(); | ||
1015 | m_tcp.Close(); | ||
1016 | } | ||
1017 | } | 384 | } |
1018 | } | 385 | } |