diff options
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar')
-rw-r--r-- | OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs | 3 | ||||
-rw-r--r-- | OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs | 860 |
2 files changed, 860 insertions, 3 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs index 37cf328..8216222 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs | |||
@@ -65,7 +65,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
65 | m_scenes.Add(scene); | 65 | m_scenes.Add(scene); |
66 | scene.EventManager.OnNewClient += NewClient; | 66 | scene.EventManager.OnNewClient += NewClient; |
67 | scene.EventManager.OnChatFromWorld += SimChat; | 67 | scene.EventManager.OnChatFromWorld += SimChat; |
68 | // scene.RegisterModuleInterface<ISimChat>(this); | ||
69 | } | 68 | } |
70 | 69 | ||
71 | // wrap this in a try block so that defaults will work if | 70 | // wrap this in a try block so that defaults will work if |
@@ -108,8 +107,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
108 | public void SimChat(Object sender, ChatFromViewerArgs e) | 107 | public void SimChat(Object sender, ChatFromViewerArgs e) |
109 | { | 108 | { |
110 | ScenePresence avatar = null; | 109 | ScenePresence avatar = null; |
111 | |||
112 | //TODO: Move ForEachScenePresence and others into IScene. | ||
113 | Scene scene = (Scene) e.Scene; | 110 | Scene scene = (Scene) e.Scene; |
114 | 111 | ||
115 | //TODO: Remove the need for this check | 112 | //TODO: Remove the need for this check |
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs new file mode 100644 index 0000000..0b5f1b4 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs | |||
@@ -0,0 +1,860 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSim Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Net.Sockets; | ||
32 | using System.Reflection; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using System.Threading; | ||
35 | using libsecondlife; | ||
36 | using log4net; | ||
37 | using Nini.Config; | ||
38 | using OpenSim.Framework; | ||
39 | using OpenSim.Region.Environment.Interfaces; | ||
40 | using OpenSim.Region.Environment.Scenes; | ||
41 | |||
42 | namespace OpenSim.Region.Environment.Modules.Avatar.Chat | ||
43 | { | ||
44 | public class IRCBridgeModule : IRegionModule, ISimChat | ||
45 | { | ||
46 | private static readonly ILog m_log = | ||
47 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
48 | |||
49 | private const int DEBUG_CHANNEL = 2147483647; | ||
50 | |||
51 | private string m_defaultzone = null; | ||
52 | |||
53 | private IRCChatModule m_irc = null; | ||
54 | private Thread m_irc_connector = null; | ||
55 | |||
56 | private string m_last_leaving_user = null; | ||
57 | private string m_last_new_user = null; | ||
58 | private List<Scene> m_scenes = new List<Scene>(); | ||
59 | |||
60 | internal object m_syncInit = new object(); | ||
61 | internal object m_syncLogout = new object(); | ||
62 | |||
63 | #region IRegionModule Members | ||
64 | |||
65 | public void Initialise(Scene scene, IConfigSource config) | ||
66 | { | ||
67 | lock (m_syncInit) | ||
68 | { | ||
69 | if (!m_scenes.Contains(scene)) | ||
70 | { | ||
71 | m_scenes.Add(scene); | ||
72 | scene.EventManager.OnNewClient += NewClient; | ||
73 | scene.EventManager.OnChatFromWorld += SimChat; | ||
74 | scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; | ||
75 | scene.EventManager.OnMakeChildAgent += OnMakeChildAgent; | ||
76 | } | ||
77 | |||
78 | try | ||
79 | { | ||
80 | m_defaultzone = config.Configs["IRC"].GetString("fallback_region", "Sim"); | ||
81 | } | ||
82 | catch (Exception) | ||
83 | { | ||
84 | } | ||
85 | |||
86 | // setup IRC Relay | ||
87 | if (m_irc == null) | ||
88 | { | ||
89 | m_irc = new IRCChatModule(config); | ||
90 | } | ||
91 | if (m_irc_connector == null) | ||
92 | { | ||
93 | m_irc_connector = new Thread(IRCConnectRun); | ||
94 | m_irc_connector.Name = "IRCConnectorThread"; | ||
95 | m_irc_connector.IsBackground = true; | ||
96 | } | ||
97 | m_log.InfoFormat("[IRC] initialized for {0}, nick: {1} ", scene.RegionInfo.RegionName, | ||
98 | m_defaultzone); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | public void PostInitialise() | ||
103 | { | ||
104 | if (m_irc.Enabled) | ||
105 | { | ||
106 | try | ||
107 | { | ||
108 | //m_irc.Connect(m_scenes); | ||
109 | if (m_irc_connector == null) | ||
110 | { | ||
111 | m_irc_connector = new Thread(IRCConnectRun); | ||
112 | m_irc_connector.Name = "IRCConnectorThread"; | ||
113 | m_irc_connector.IsBackground = true; | ||
114 | } | ||
115 | if (!m_irc_connector.IsAlive) | ||
116 | { | ||
117 | m_irc_connector.Start(); | ||
118 | ThreadTracker.Add(m_irc_connector); | ||
119 | } | ||
120 | } | ||
121 | catch (Exception) | ||
122 | { | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | public void Close() | ||
128 | { | ||
129 | m_irc.Close(); | ||
130 | } | ||
131 | |||
132 | public string Name | ||
133 | { | ||
134 | get { return "IRCBridgeModule"; } | ||
135 | } | ||
136 | |||
137 | public bool IsSharedModule | ||
138 | { | ||
139 | get { return true; } | ||
140 | } | ||
141 | |||
142 | #endregion | ||
143 | |||
144 | #region ISimChat Members | ||
145 | |||
146 | public void SimChat(Object sender, ChatFromViewerArgs e) | ||
147 | { | ||
148 | ScenePresence avatar = null; | ||
149 | Scene scene = (Scene) e.Scene; | ||
150 | |||
151 | if (scene == null) | ||
152 | scene = m_scenes[0]; | ||
153 | |||
154 | // Filled in since it's easier than rewriting right now. | ||
155 | string fromName = e.From; | ||
156 | |||
157 | if (e.Sender != null) | ||
158 | { | ||
159 | avatar = scene.GetScenePresence(e.Sender.AgentId); | ||
160 | } | ||
161 | |||
162 | if (avatar != null) | ||
163 | { | ||
164 | fromName = avatar.Firstname + " " + avatar.Lastname; | ||
165 | } | ||
166 | |||
167 | // Try to reconnect to server if not connected | ||
168 | if (m_irc.Enabled && !m_irc.Connected) | ||
169 | { | ||
170 | // In a non-blocking way. Eventually the connector will get it started | ||
171 | try | ||
172 | { | ||
173 | if (m_irc_connector == null) | ||
174 | { | ||
175 | m_irc_connector = new Thread(IRCConnectRun); | ||
176 | m_irc_connector.Name = "IRCConnectorThread"; | ||
177 | m_irc_connector.IsBackground = true; | ||
178 | } | ||
179 | if (!m_irc_connector.IsAlive) | ||
180 | { | ||
181 | m_irc_connector.Start(); | ||
182 | ThreadTracker.Add(m_irc_connector); | ||
183 | } | ||
184 | } | ||
185 | catch (Exception) | ||
186 | { | ||
187 | } | ||
188 | } | ||
189 | |||
190 | |||
191 | // We only want to relay stuff on channel 0 | ||
192 | if (e.Channel == 0 || e.Channel == DEBUG_CHANNEL) | ||
193 | { | ||
194 | if (e.Channel == DEBUG_CHANNEL) | ||
195 | e.Type = ChatTypeEnum.DebugChannel; | ||
196 | |||
197 | // IRC stuff | ||
198 | if (e.Message.Length > 0 && e.Channel == 0) | ||
199 | { | ||
200 | if (m_irc.Connected && (avatar != null)) // this is to keep objects from talking to IRC | ||
201 | { | ||
202 | m_irc.PrivMsg(fromName, scene.RegionInfo.RegionName, e.Message); | ||
203 | } | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | |||
208 | #endregion | ||
209 | |||
210 | public void NewClient(IClientAPI client) | ||
211 | { | ||
212 | try | ||
213 | { | ||
214 | string clientName = String.Format("{0} {1}", client.FirstName, client.LastName); | ||
215 | |||
216 | client.OnChatFromViewer += SimChat; | ||
217 | client.OnLogout += ClientLoggedOut; | ||
218 | client.OnConnectionClosed += ClientLoggedOut; | ||
219 | |||
220 | if (clientName != m_last_new_user) | ||
221 | { | ||
222 | if ((m_irc.Enabled) && (m_irc.Connected)) | ||
223 | { | ||
224 | m_log.DebugFormat("[IRC] {0} logging on", clientName); | ||
225 | m_irc.PrivMsg(m_irc.Nick, "Sim", | ||
226 | String.Format("notices {0} logging on", clientName)); | ||
227 | } | ||
228 | m_last_new_user = clientName; | ||
229 | } | ||
230 | } | ||
231 | catch (Exception ex) | ||
232 | { | ||
233 | m_log.Error("[IRC]: NewClient exception trap:" + ex.ToString()); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | public void OnMakeRootAgent(ScenePresence presence) | ||
238 | { | ||
239 | try | ||
240 | { | ||
241 | if ((m_irc.Enabled) && (m_irc.Connected)) | ||
242 | { | ||
243 | string regionName = presence.Scene.RegionInfo.RegionName; | ||
244 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); | ||
245 | m_log.DebugFormat("[IRC] noticing {0} in {1}", clientName, regionName); | ||
246 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} in {1}", clientName, regionName)); | ||
247 | } | ||
248 | } | ||
249 | catch (Exception ex) | ||
250 | { | ||
251 | } | ||
252 | } | ||
253 | |||
254 | public void OnMakeChildAgent(ScenePresence presence) | ||
255 | { | ||
256 | try | ||
257 | { | ||
258 | if ((m_irc.Enabled) && (m_irc.Connected)) | ||
259 | { | ||
260 | string regionName = presence.Scene.RegionInfo.RegionName; | ||
261 | string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); | ||
262 | m_log.DebugFormat("[IRC] noticing {0} in {1}", clientName, regionName); | ||
263 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} left {1}", clientName, regionName)); | ||
264 | } | ||
265 | } | ||
266 | catch (Exception ex) | ||
267 | { | ||
268 | } | ||
269 | } | ||
270 | |||
271 | |||
272 | public void ClientLoggedOut(IClientAPI client) | ||
273 | { | ||
274 | lock (m_syncLogout) | ||
275 | { | ||
276 | try | ||
277 | { | ||
278 | if ((m_irc.Enabled) && (m_irc.Connected)) | ||
279 | { | ||
280 | string clientName = String.Format("{0} {1}", client.FirstName, client.LastName); | ||
281 | // handles simple case. May not work for hundred connecting in per second. | ||
282 | // and the NewClients calles getting interleved | ||
283 | // but filters out multiple reports | ||
284 | if (clientName != m_last_leaving_user) | ||
285 | { | ||
286 | m_last_leaving_user = clientName; | ||
287 | m_irc.PrivMsg(m_irc.Nick, "Sim", String.Format("notices {0} logging out", clientName)); | ||
288 | m_log.InfoFormat("[IRC]: {0} logging out", clientName); | ||
289 | } | ||
290 | |||
291 | if (m_last_new_user == clientName) | ||
292 | m_last_new_user = null; | ||
293 | } | ||
294 | } | ||
295 | catch (Exception ex) | ||
296 | { | ||
297 | m_log.Error("[IRC]: ClientLoggedOut exception trap:" + ex.ToString()); | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | |||
302 | // if IRC is enabled then just keep trying using a monitor thread | ||
303 | public void IRCConnectRun() | ||
304 | { | ||
305 | while (true) | ||
306 | { | ||
307 | if ((m_irc.Enabled) && (!m_irc.Connected)) | ||
308 | { | ||
309 | m_irc.Connect(m_scenes); | ||
310 | } | ||
311 | Thread.Sleep(15000); | ||
312 | } | ||
313 | } | ||
314 | |||
315 | public string FindClientRegion(string firstName, string lastName, bool ignoreChilds) | ||
316 | { | ||
317 | string sourceRegion = null; | ||
318 | foreach (Scene s in m_scenes) | ||
319 | { | ||
320 | s.ForEachScenePresence(delegate(ScenePresence presence) | ||
321 | { | ||
322 | if (ignoreChilds | ||
323 | && (presence.Firstname == firstName) | ||
324 | && (presence.Lastname == lastName)) | ||
325 | { | ||
326 | sourceRegion = presence.Scene.RegionInfo.RegionName; | ||
327 | return; | ||
328 | } | ||
329 | |||
330 | if (!ignoreChilds && !presence.IsChildAgent | ||
331 | && (presence.Firstname == firstName) | ||
332 | && (presence.Lastname == lastName)) | ||
333 | { | ||
334 | sourceRegion = presence.Scene.RegionInfo.RegionName; | ||
335 | return; | ||
336 | } | ||
337 | }); | ||
338 | if (sourceRegion != null) return sourceRegion; | ||
339 | } | ||
340 | if (m_defaultzone == null) | ||
341 | { | ||
342 | m_defaultzone = "Sim"; | ||
343 | } | ||
344 | return m_defaultzone; | ||
345 | } | ||
346 | } | ||
347 | |||
348 | internal class IRCChatModule | ||
349 | { | ||
350 | #region ErrorReplies enum | ||
351 | |||
352 | public enum ErrorReplies | ||
353 | { | ||
354 | NotRegistered = 451, // ":You have not registered" | ||
355 | NicknameInUse = 433 // "<nick> :Nickname is already in use" | ||
356 | } | ||
357 | |||
358 | #endregion | ||
359 | |||
360 | #region Replies enum | ||
361 | |||
362 | public enum Replies | ||
363 | { | ||
364 | MotdStart = 375, // ":- <server> Message of the day - " | ||
365 | Motd = 372, // ":- <text>" | ||
366 | EndOfMotd = 376 // ":End of /MOTD command" | ||
367 | } | ||
368 | |||
369 | #endregion | ||
370 | |||
371 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
372 | private Thread listener; | ||
373 | |||
374 | private string m_basenick = null; | ||
375 | private string m_channel = null; | ||
376 | private bool m_connected = false; | ||
377 | private bool m_enabled = false; | ||
378 | private List<Scene> m_last_scenes = null; | ||
379 | private string m_nick = null; | ||
380 | private uint m_port = 6668; | ||
381 | private string m_privmsgformat = "PRIVMSG {0} :<{1} in {2}>: {3}"; | ||
382 | private StreamReader m_reader; | ||
383 | private List<Scene> m_scenes = null; | ||
384 | private string m_server = null; | ||
385 | |||
386 | private NetworkStream m_stream; | ||
387 | internal object m_syncConnect = new object(); | ||
388 | private TcpClient m_tcp; | ||
389 | private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to irc bot"; | ||
390 | private StreamWriter m_writer; | ||
391 | |||
392 | private Thread pingSender; | ||
393 | |||
394 | public IRCChatModule(IConfigSource config) | ||
395 | { | ||
396 | m_nick = "OSimBot" + Util.RandomClass.Next(1, 99); | ||
397 | m_tcp = null; | ||
398 | m_writer = null; | ||
399 | m_reader = null; | ||
400 | |||
401 | // configuration in OpenSim.ini | ||
402 | // [IRC] | ||
403 | // server = chat.freenode.net | ||
404 | // nick = OSimBot_mysim | ||
405 | // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot | ||
406 | // ; username is the IRC command line sent | ||
407 | // ; USER <irc_user> <visible=8,invisible=0> * : <IRC_realname> | ||
408 | // channel = #opensim-regions | ||
409 | // port = 6667 | ||
410 | // ;MSGformat fields : 0=botnick, 1=user, 2=region, 3=message | ||
411 | // ;for <bot>:<user in region> :<message> | ||
412 | // ;msgformat = "PRIVMSG {0} :<{1} in {2}>: {3}" | ||
413 | // ;for <bot>:<message> - <user of region> : | ||
414 | // ;msgformat = "PRIVMSG {0} : {3} - {1} of {2}" | ||
415 | // ;for <bot>:<message> - from <user> : | ||
416 | // ;msgformat = "PRIVMSG {0} : {3} - from {1}" | ||
417 | // Traps I/O disconnects so it does not crash the sim | ||
418 | // Trys to reconnect if disconnected and someone says something | ||
419 | // Tells IRC server "QUIT" when doing a close (just to be nice) | ||
420 | // Default port back to 6667 | ||
421 | |||
422 | try | ||
423 | { | ||
424 | m_server = config.Configs["IRC"].GetString("server"); | ||
425 | m_nick = config.Configs["IRC"].GetString("nick"); | ||
426 | m_basenick = m_nick; | ||
427 | m_channel = config.Configs["IRC"].GetString("channel"); | ||
428 | m_port = (uint) config.Configs["IRC"].GetInt("port", (int) m_port); | ||
429 | m_user = config.Configs["IRC"].GetString("username", m_user); | ||
430 | m_privmsgformat = config.Configs["IRC"].GetString("msgformat", m_privmsgformat); | ||
431 | if (m_server != null && m_nick != null && m_channel != null) | ||
432 | { | ||
433 | m_nick = m_nick + Util.RandomClass.Next(1, 99); | ||
434 | m_enabled = true; | ||
435 | } | ||
436 | } | ||
437 | catch (Exception) | ||
438 | { | ||
439 | m_log.Info("[CHAT]: No IRC config information, skipping IRC bridge configuration"); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | public bool Enabled | ||
444 | { | ||
445 | get { return m_enabled; } | ||
446 | } | ||
447 | |||
448 | public bool Connected | ||
449 | { | ||
450 | get { return m_connected; } | ||
451 | } | ||
452 | |||
453 | public string Nick | ||
454 | { | ||
455 | get { return m_nick; } | ||
456 | } | ||
457 | |||
458 | public bool Connect(List<Scene> scenes) | ||
459 | { | ||
460 | lock (m_syncConnect) | ||
461 | { | ||
462 | try | ||
463 | { | ||
464 | if (m_connected) return true; | ||
465 | m_scenes = scenes; | ||
466 | if (m_last_scenes == null) | ||
467 | { | ||
468 | m_last_scenes = scenes; | ||
469 | } | ||
470 | |||
471 | m_tcp = new TcpClient(m_server, (int) m_port); | ||
472 | m_log.Info("[IRC]: Connecting..."); | ||
473 | m_stream = m_tcp.GetStream(); | ||
474 | m_log.Info("[IRC]: Connected to " + m_server); | ||
475 | m_reader = new StreamReader(m_stream); | ||
476 | m_writer = new StreamWriter(m_stream); | ||
477 | |||
478 | pingSender = new Thread(new ThreadStart(PingRun)); | ||
479 | pingSender.Name = "PingSenderThread"; | ||
480 | pingSender.IsBackground = true; | ||
481 | pingSender.Start(); | ||
482 | ThreadTracker.Add(pingSender); | ||
483 | |||
484 | listener = new Thread(new ThreadStart(ListenerRun)); | ||
485 | listener.Name = "IRCChatModuleListenerThread"; | ||
486 | listener.IsBackground = true; | ||
487 | listener.Start(); | ||
488 | ThreadTracker.Add(listener); | ||
489 | |||
490 | m_writer.WriteLine(m_user); | ||
491 | m_writer.Flush(); | ||
492 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
493 | m_writer.Flush(); | ||
494 | m_writer.WriteLine(String.Format("JOIN {0}", m_channel)); | ||
495 | m_writer.Flush(); | ||
496 | m_log.Info("[IRC]: Connection fully established"); | ||
497 | m_connected = true; | ||
498 | } | ||
499 | catch (Exception e) | ||
500 | { | ||
501 | Console.WriteLine(e.ToString()); | ||
502 | } | ||
503 | return m_connected; | ||
504 | } | ||
505 | } | ||
506 | |||
507 | public void Reconnect() | ||
508 | { | ||
509 | m_connected = false; | ||
510 | listener.Abort(); | ||
511 | pingSender.Abort(); | ||
512 | m_writer.Close(); | ||
513 | m_reader.Close(); | ||
514 | m_tcp.Close(); | ||
515 | if (m_enabled) | ||
516 | { | ||
517 | Connect(m_last_scenes); | ||
518 | } | ||
519 | } | ||
520 | |||
521 | public void PrivMsg(string from, string region, string msg) | ||
522 | { | ||
523 | // One message to the IRC server | ||
524 | try | ||
525 | { | ||
526 | m_writer.WriteLine(m_privmsgformat, m_channel, from, region, msg); | ||
527 | m_writer.Flush(); | ||
528 | m_log.InfoFormat("[IRC]: PrivMsg {0} in {1}: {2}", from, region, msg); | ||
529 | } | ||
530 | catch (IOException) | ||
531 | { | ||
532 | m_log.Error("[IRC]: Disconnected from IRC server.(PrivMsg)"); | ||
533 | Reconnect(); | ||
534 | } | ||
535 | catch (Exception ex) | ||
536 | { | ||
537 | m_log.ErrorFormat("[IRC]: PrivMsg exception trap: {0}", ex.ToString()); | ||
538 | } | ||
539 | } | ||
540 | |||
541 | private Dictionary<string, string> ExtractMsg(string input) | ||
542 | { | ||
543 | //examines IRC commands and extracts any private messages | ||
544 | // which will then be reboadcast in the Sim | ||
545 | |||
546 | m_log.Info("[IRC]: ExtractMsg: " + input); | ||
547 | Dictionary<string, string> result = null; | ||
548 | //string regex = @":(?<nick>\w*)!~(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)"; | ||
549 | string regex = @":(?<nick>\w*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)"; | ||
550 | Regex RE = new Regex(regex, RegexOptions.Multiline); | ||
551 | MatchCollection matches = RE.Matches(input); | ||
552 | // Get some direct matches $1 $4 is a | ||
553 | if ((matches.Count == 1) && (matches[0].Groups.Count == 5)) | ||
554 | { | ||
555 | result = new Dictionary<string, string>(); | ||
556 | result.Add("nick", matches[0].Groups[1].Value); | ||
557 | result.Add("user", matches[0].Groups[2].Value); | ||
558 | result.Add("channel", matches[0].Groups[3].Value); | ||
559 | result.Add("msg", matches[0].Groups[4].Value); | ||
560 | } | ||
561 | else | ||
562 | { | ||
563 | m_log.Info("[IRC]: Number of matches: " + matches.Count); | ||
564 | if (matches.Count > 0) | ||
565 | { | ||
566 | m_log.Info("[IRC]: Number of groups: " + matches[0].Groups.Count); | ||
567 | } | ||
568 | } | ||
569 | return result; | ||
570 | } | ||
571 | |||
572 | public void PingRun() | ||
573 | { | ||
574 | // IRC keep alive thread | ||
575 | // send PING ever 15 seconds | ||
576 | while (true) | ||
577 | { | ||
578 | try | ||
579 | { | ||
580 | if (m_connected == true) | ||
581 | { | ||
582 | m_writer.WriteLine(String.Format("PING :{0}", m_server)); | ||
583 | m_writer.Flush(); | ||
584 | Thread.Sleep(15000); | ||
585 | } | ||
586 | } | ||
587 | catch (IOException) | ||
588 | { | ||
589 | m_log.Error("[IRC]: Disconnected from IRC server.(PingRun)"); | ||
590 | Reconnect(); | ||
591 | } | ||
592 | catch (Exception ex) | ||
593 | { | ||
594 | m_log.ErrorFormat("[IRC]: PingRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
595 | } | ||
596 | } | ||
597 | } | ||
598 | |||
599 | public void ListenerRun() | ||
600 | { | ||
601 | string inputLine; | ||
602 | LLVector3 pos = new LLVector3(128, 128, 20); | ||
603 | while (true) | ||
604 | { | ||
605 | try | ||
606 | { | ||
607 | while ((m_connected == true) && ((inputLine = m_reader.ReadLine()) != null)) | ||
608 | { | ||
609 | // Console.WriteLine(inputLine); | ||
610 | if (inputLine.Contains(m_channel)) | ||
611 | { | ||
612 | Dictionary<string, string> data = ExtractMsg(inputLine); | ||
613 | // Any chat ??? | ||
614 | if (data != null) | ||
615 | { | ||
616 | foreach (Scene m_scene in m_scenes) | ||
617 | { | ||
618 | m_scene.ForEachScenePresence(delegate(ScenePresence avatar) | ||
619 | { | ||
620 | if (!avatar.IsChildAgent) | ||
621 | { | ||
622 | avatar.ControllingClient.SendChatMessage( | ||
623 | Helpers.StringToField(data["msg"]), | ||
624 | 1, // 255, | ||
625 | pos, data["nick"], | ||
626 | LLUUID.Zero,(byte)ChatSourceType.Agent,(byte)ChatAudibleLevel.Fully); | ||
627 | } | ||
628 | }); | ||
629 | } | ||
630 | } | ||
631 | else | ||
632 | { | ||
633 | // Was an command from the IRC server | ||
634 | ProcessIRCCommand(inputLine); | ||
635 | } | ||
636 | } | ||
637 | else | ||
638 | { | ||
639 | // Was an command from the IRC server | ||
640 | ProcessIRCCommand(inputLine); | ||
641 | } | ||
642 | Thread.Sleep(150); | ||
643 | } | ||
644 | } | ||
645 | catch (IOException) | ||
646 | { | ||
647 | m_log.Error("[IRC]: ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)"); | ||
648 | Reconnect(); | ||
649 | } | ||
650 | catch (Exception ex) | ||
651 | { | ||
652 | m_log.ErrorFormat("[IRC]: ListenerRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
653 | } | ||
654 | } | ||
655 | } | ||
656 | |||
657 | public void BroadcastSim(string sender, string format, params string[] args) | ||
658 | { | ||
659 | LLVector3 pos = new LLVector3(128, 128, 20); | ||
660 | try | ||
661 | { | ||
662 | foreach (Scene m_scene in m_scenes) | ||
663 | { | ||
664 | m_scene.ForEachScenePresence(delegate(ScenePresence avatar) | ||
665 | { | ||
666 | if (!avatar.IsChildAgent) | ||
667 | { | ||
668 | avatar.ControllingClient.SendChatMessage( | ||
669 | Helpers.StringToField(String.Format(format, args)), | ||
670 | 1, //255, | ||
671 | pos, sender, LLUUID.Zero, | ||
672 | (byte)ChatSourceType.Object, | ||
673 | (byte)ChatAudibleLevel.Fully); | ||
674 | } | ||
675 | }); | ||
676 | } | ||
677 | } | ||
678 | catch (Exception ex) // IRC gate should not crash Sim | ||
679 | { | ||
680 | m_log.ErrorFormat("[IRC]: BroadcastSim Exception Trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
681 | } | ||
682 | } | ||
683 | |||
684 | public void ProcessIRCCommand(string command) | ||
685 | { | ||
686 | //m_log.Info("[IRC]: ProcessIRCCommand:" + command); | ||
687 | |||
688 | string[] commArgs = new string[command.Split(' ').Length]; | ||
689 | string c_server = m_server; | ||
690 | |||
691 | commArgs = command.Split(' '); | ||
692 | if (commArgs[0].Substring(0, 1) == ":") | ||
693 | { | ||
694 | commArgs[0] = commArgs[0].Remove(0, 1); | ||
695 | } | ||
696 | |||
697 | if (commArgs[1] == "002") | ||
698 | { | ||
699 | // fetch the correct servername | ||
700 | // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/... | ||
701 | // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch | ||
702 | |||
703 | c_server = (commArgs[6].Split('['))[0]; | ||
704 | m_server = c_server; | ||
705 | } | ||
706 | |||
707 | if (commArgs[0] == "ERROR") | ||
708 | { | ||
709 | m_log.ErrorFormat("[IRC]: IRC SERVER ERROR: {0}", command); | ||
710 | } | ||
711 | |||
712 | if (commArgs[0] == "PING") | ||
713 | { | ||
714 | string p_reply = ""; | ||
715 | |||
716 | for (int i = 1; i < commArgs.Length; i++) | ||
717 | { | ||
718 | p_reply += commArgs[i] + " "; | ||
719 | } | ||
720 | |||
721 | m_writer.WriteLine(String.Format("PONG {0}", p_reply)); | ||
722 | m_writer.Flush(); | ||
723 | } | ||
724 | else if (commArgs[0] == c_server) | ||
725 | { | ||
726 | // server message | ||
727 | try | ||
728 | { | ||
729 | Int32 commandCode = Int32.Parse(commArgs[1]); | ||
730 | switch (commandCode) | ||
731 | { | ||
732 | case (int) ErrorReplies.NicknameInUse: | ||
733 | // Gen a new name | ||
734 | m_nick = m_basenick + Util.RandomClass.Next(1, 99); | ||
735 | m_log.ErrorFormat("[IRC]: IRC SERVER reports NicknameInUse, trying {0}", m_nick); | ||
736 | // Retry | ||
737 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
738 | m_writer.Flush(); | ||
739 | m_writer.WriteLine(String.Format("JOIN {0}", m_channel)); | ||
740 | m_writer.Flush(); | ||
741 | break; | ||
742 | case (int) ErrorReplies.NotRegistered: | ||
743 | break; | ||
744 | case (int) Replies.EndOfMotd: | ||
745 | break; | ||
746 | } | ||
747 | } | ||
748 | catch (Exception) | ||
749 | { | ||
750 | } | ||
751 | } | ||
752 | else | ||
753 | { | ||
754 | // Normal message | ||
755 | string commAct = commArgs[1]; | ||
756 | switch (commAct) | ||
757 | { | ||
758 | case "JOIN": | ||
759 | eventIrcJoin(commArgs); | ||
760 | break; | ||
761 | case "PART": | ||
762 | eventIrcPart(commArgs); | ||
763 | break; | ||
764 | case "MODE": | ||
765 | eventIrcMode(commArgs); | ||
766 | break; | ||
767 | case "NICK": | ||
768 | eventIrcNickChange(commArgs); | ||
769 | break; | ||
770 | case "KICK": | ||
771 | eventIrcKick(commArgs); | ||
772 | break; | ||
773 | case "QUIT": | ||
774 | eventIrcQuit(commArgs); | ||
775 | break; | ||
776 | case "PONG": | ||
777 | break; // that's nice | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | |||
782 | public void eventIrcJoin(string[] commArgs) | ||
783 | { | ||
784 | string IrcChannel = commArgs[2]; | ||
785 | string IrcUser = commArgs[0].Split('!')[0]; | ||
786 | BroadcastSim(m_nick, "{0} is joining {1}", IrcUser, IrcChannel); | ||
787 | } | ||
788 | |||
789 | public void eventIrcPart(string[] commArgs) | ||
790 | { | ||
791 | string IrcChannel = commArgs[2]; | ||
792 | string IrcUser = commArgs[0].Split('!')[0]; | ||
793 | BroadcastSim(m_nick, "{0} is parting {1}", IrcUser, IrcChannel); | ||
794 | } | ||
795 | |||
796 | public void eventIrcMode(string[] commArgs) | ||
797 | { | ||
798 | string IrcChannel = commArgs[2]; | ||
799 | string IrcUser = commArgs[0].Split('!')[0]; | ||
800 | string UserMode = ""; | ||
801 | for (int i = 3; i < commArgs.Length; i++) | ||
802 | { | ||
803 | UserMode += commArgs[i] + " "; | ||
804 | } | ||
805 | |||
806 | if (UserMode.Substring(0, 1) == ":") | ||
807 | { | ||
808 | UserMode = UserMode.Remove(0, 1); | ||
809 | } | ||
810 | } | ||
811 | |||
812 | public void eventIrcNickChange(string[] commArgs) | ||
813 | { | ||
814 | string UserOldNick = commArgs[0].Split('!')[0]; | ||
815 | string UserNewNick = commArgs[2].Remove(0, 1); | ||
816 | BroadcastSim(m_nick, "{0} changed their nick to {1}", UserOldNick, UserNewNick); | ||
817 | } | ||
818 | |||
819 | public void eventIrcKick(string[] commArgs) | ||
820 | { | ||
821 | string UserKicker = commArgs[0].Split('!')[0]; | ||
822 | string UserKicked = commArgs[3]; | ||
823 | string IrcChannel = commArgs[2]; | ||
824 | string KickMessage = ""; | ||
825 | for (int i = 4; i < commArgs.Length; i++) | ||
826 | { | ||
827 | KickMessage += commArgs[i] + " "; | ||
828 | } | ||
829 | BroadcastSim(m_nick, "{0} kicked {1} on {2} saying {3}", UserKicker, UserKicked, IrcChannel, KickMessage); | ||
830 | if (UserKicked == m_nick) | ||
831 | { | ||
832 | BroadcastSim(m_nick, "Hey, that was me!!!"); | ||
833 | } | ||
834 | } | ||
835 | |||
836 | public void eventIrcQuit(string[] commArgs) | ||
837 | { | ||
838 | string IrcUser = commArgs[0].Split('!')[0]; | ||
839 | string QuitMessage = ""; | ||
840 | |||
841 | for (int i = 2; i < commArgs.Length; i++) | ||
842 | { | ||
843 | QuitMessage += commArgs[i] + " "; | ||
844 | } | ||
845 | BroadcastSim(m_nick, "{0} quits saying {1}", IrcUser, QuitMessage); | ||
846 | } | ||
847 | |||
848 | public void Close() | ||
849 | { | ||
850 | m_connected = false; | ||
851 | m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole with {2} closing", m_nick, m_channel, m_server)); | ||
852 | m_writer.Flush(); | ||
853 | listener.Abort(); | ||
854 | pingSender.Abort(); | ||
855 | m_writer.Close(); | ||
856 | m_reader.Close(); | ||
857 | m_tcp.Close(); | ||
858 | } | ||
859 | } | ||
860 | } \ No newline at end of file | ||