diff options
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar/Chat')
3 files changed, 936 insertions, 813 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs index 34a604e..5ee07ff 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs | |||
@@ -48,6 +48,7 @@ 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 bool m_enabled = true; | ||
51 | private int m_saydistance = 30; | 52 | private int m_saydistance = 30; |
52 | private int m_shoutdistance = 100; | 53 | private int m_shoutdistance = 100; |
53 | private int m_whisperdistance = 10; | 54 | private int m_whisperdistance = 10; |
@@ -56,23 +57,15 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
56 | internal object m_syncInit = new object(); | 57 | internal object m_syncInit = new object(); |
57 | 58 | ||
58 | #region IRegionModule Members | 59 | #region IRegionModule Members |
59 | public void Initialise(Scene scene, IConfigSource config) | 60 | public virtual void Initialise(Scene scene, IConfigSource config) |
60 | { | 61 | { |
61 | lock (m_syncInit) | ||
62 | { | ||
63 | if (!m_scenes.Contains(scene)) | ||
64 | { | ||
65 | m_scenes.Add(scene); | ||
66 | scene.EventManager.OnNewClient += OnNewClient; | ||
67 | scene.EventManager.OnChatFromWorld += OnChatFromWorld; | ||
68 | scene.EventManager.OnChatBroadcast += OnChatBroadcast; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | // wrap this in a try block so that defaults will work if | 62 | // wrap this in a try block so that defaults will work if |
73 | // the config file doesn't specify otherwise. | 63 | // the config file doesn't specify otherwise. |
74 | try | 64 | try |
75 | { | 65 | { |
66 | m_enabled = config.Configs["Chat"].GetBoolean("enabled", m_enabled); | ||
67 | if (!m_enabled) return; | ||
68 | |||
76 | m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); | 69 | m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); |
77 | m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); | 70 | m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); |
78 | m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); | 71 | m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); |
@@ -80,23 +73,35 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
80 | catch (Exception) | 73 | catch (Exception) |
81 | { | 74 | { |
82 | } | 75 | } |
76 | |||
77 | lock (m_syncInit) | ||
78 | { | ||
79 | if (!m_scenes.Contains(scene)) | ||
80 | { | ||
81 | m_scenes.Add(scene); | ||
82 | scene.EventManager.OnNewClient += OnNewClient; | ||
83 | scene.EventManager.OnChatFromWorld += OnChatFromWorld; | ||
84 | scene.EventManager.OnChatBroadcast += OnChatBroadcast; | ||
85 | } | ||
86 | } | ||
87 | |||
83 | m_log.InfoFormat("[CHAT] initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, | 88 | m_log.InfoFormat("[CHAT] initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, |
84 | m_whisperdistance, m_saydistance, m_shoutdistance); | 89 | m_whisperdistance, m_saydistance, m_shoutdistance); |
85 | } | 90 | } |
86 | public void PostInitialise() | 91 | public virtual void PostInitialise() |
87 | { | 92 | { |
88 | } | 93 | } |
89 | 94 | ||
90 | public void Close() | 95 | public virtual void Close() |
91 | { | 96 | { |
92 | } | 97 | } |
93 | 98 | ||
94 | public string Name | 99 | public virtual string Name |
95 | { | 100 | { |
96 | get { return "ChatModule"; } | 101 | get { return "ChatModule"; } |
97 | } | 102 | } |
98 | 103 | ||
99 | public bool IsSharedModule | 104 | public virtual bool IsSharedModule |
100 | { | 105 | { |
101 | get { return true; } | 106 | get { return true; } |
102 | } | 107 | } |
@@ -104,16 +109,9 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
104 | #endregion | 109 | #endregion |
105 | 110 | ||
106 | 111 | ||
107 | public void OnNewClient(IClientAPI client) | 112 | public virtual void OnNewClient(IClientAPI client) |
108 | { | 113 | { |
109 | try | 114 | client.OnChatFromClient += OnChatFromClient; |
110 | { | ||
111 | client.OnChatFromClient += OnChatFromClient; | ||
112 | } | ||
113 | catch (Exception ex) | ||
114 | { | ||
115 | m_log.Error("[CHAT]: NewClient exception trap:" + ex.ToString()); | ||
116 | } | ||
117 | } | 115 | } |
118 | 116 | ||
119 | public virtual void OnChatFromClient(Object sender, OSChatMessage e) | 117 | public virtual void OnChatFromClient(Object sender, OSChatMessage e) |
@@ -132,45 +130,76 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
132 | return; | 130 | return; |
133 | } | 131 | } |
134 | 132 | ||
135 | string message = e.Message; | 133 | // string message = e.Message; |
136 | if (e.Channel == DEBUG_CHANNEL) e.Type = ChatTypeEnum.DebugChannel; | 134 | // if (e.Channel == DEBUG_CHANNEL) e.Type = ChatTypeEnum.DebugChannel; |
137 | 135 | ||
138 | ScenePresence avatar = scene.GetScenePresence(e.Sender.AgentId); | 136 | // ScenePresence avatar = scene.GetScenePresence(e.Sender.AgentId); |
139 | Vector3 fromPos = avatar.AbsolutePosition; | 137 | // Vector3 fromPos = avatar.AbsolutePosition; |
140 | Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, | 138 | // Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, |
141 | scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); | 139 | // scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); |
142 | string fromName = avatar.Firstname + " " + avatar.Lastname; | 140 | // string fromName = avatar.Firstname + " " + avatar.Lastname; |
143 | UUID fromID = e.Sender.AgentId; | 141 | // UUID fromID = e.Sender.AgentId; |
144 | 142 | ||
145 | DeliverChatToAvatars(fromPos, regionPos, fromID, fromName, e.Type, ChatSourceType.Agent, message); | 143 | // DeliverChatToAvatars(fromPos, regionPos, fromID, fromName, e.Type, ChatSourceType.Agent, message); |
144 | DeliverChatToAvatars(ChatSourceType.Agent, e); | ||
146 | } | 145 | } |
147 | 146 | ||
148 | public void OnChatFromWorld(Object sender, OSChatMessage e) | 147 | public virtual void OnChatFromWorld(Object sender, OSChatMessage e) |
149 | { | 148 | { |
150 | Scene scene = (Scene) e.Scene; | ||
151 | |||
152 | // early return if not on public or debug channel | 149 | // early return if not on public or debug channel |
153 | if (e.Channel != 0 && e.Channel != DEBUG_CHANNEL) return; | 150 | if (e.Channel != 0 && e.Channel != DEBUG_CHANNEL) return; |
154 | 151 | ||
155 | // Filled in since it's easier than rewriting right now. | 152 | // // Filled in since it's easier than rewriting right now. |
156 | Vector3 fromPos = e.Position; | 153 | // Vector3 fromPos = e.Position; |
157 | Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, | 154 | // Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, |
158 | scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); | 155 | // scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); |
159 | 156 | ||
160 | string fromName = e.From; | 157 | // string fromName = e.From; |
161 | string message = e.Message; | 158 | // string message = e.Message; |
162 | UUID fromID = e.SenderUUID; | 159 | // UUID fromID = e.SenderUUID; |
163 | 160 | ||
164 | if (e.Channel == DEBUG_CHANNEL) | 161 | // if (e.Channel == DEBUG_CHANNEL) |
165 | e.Type = ChatTypeEnum.DebugChannel; | 162 | // e.Type = ChatTypeEnum.DebugChannel; |
166 | 163 | ||
167 | DeliverChatToAvatars(fromPos, regionPos, fromID, fromName, e.Type, ChatSourceType.Object, message); | 164 | // DeliverChatToAvatars(fromPos, regionPos, fromID, fromName, e.Type, ChatSourceType.Object, message); |
165 | DeliverChatToAvatars(ChatSourceType.Object, e); | ||
168 | } | 166 | } |
169 | 167 | ||
170 | protected void DeliverChatToAvatars(Vector3 pos, Vector3 regionPos, UUID uuid, string name, | 168 | protected virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c) |
171 | ChatTypeEnum chatType, ChatSourceType sourceType, string message) | ||
172 | { | 169 | { |
173 | // iterate over message | 170 | string fromName = c.From; |
171 | UUID fromID = UUID.Zero; | ||
172 | string message = c.Message; | ||
173 | IScene scene = c.Scene; | ||
174 | Vector3 fromPos = c.Position; | ||
175 | Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, | ||
176 | scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); | ||
177 | |||
178 | if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel; | ||
179 | |||
180 | switch (sourceType) | ||
181 | { | ||
182 | case ChatSourceType.Agent: | ||
183 | if (!(scene is Scene)) | ||
184 | { | ||
185 | m_log.WarnFormat("[CHAT] scene {0} is not a Scene object, cannot obtain scene presence for {1}", | ||
186 | scene.RegionInfo.RegionName, c.Sender.AgentId); | ||
187 | return; | ||
188 | } | ||
189 | ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId); | ||
190 | fromPos = avatar.AbsolutePosition; | ||
191 | fromName = avatar.Firstname + " " + avatar.Lastname; | ||
192 | fromID = c.Sender.AgentId; | ||
193 | |||
194 | break; | ||
195 | |||
196 | case ChatSourceType.Object: | ||
197 | fromID = c.SenderUUID; | ||
198 | |||
199 | break; | ||
200 | } | ||
201 | |||
202 | // TODO: iterate over message | ||
174 | if (message.Length >= 1000) // libomv limit | 203 | if (message.Length >= 1000) // libomv limit |
175 | message = message.Substring(0, 1000); | 204 | message = message.Substring(0, 1000); |
176 | 205 | ||
@@ -178,20 +207,42 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
178 | { | 207 | { |
179 | s.ForEachScenePresence(delegate(ScenePresence presence) | 208 | s.ForEachScenePresence(delegate(ScenePresence presence) |
180 | { | 209 | { |
181 | TrySendChatMessage(presence, pos, regionPos, uuid, name, | 210 | TrySendChatMessage(presence, fromPos, regionPos, fromID, fromName, |
182 | chatType, message, sourceType); | 211 | c.Type, message, sourceType); |
183 | }); | 212 | }); |
184 | } | 213 | } |
185 | } | 214 | } |
186 | 215 | ||
187 | 216 | // protected virtual void DeliverChatToAvatars(Vector3 pos, Vector3 regionPos, UUID uuid, string name, | |
188 | public void OnChatBroadcast(Object sender, OSChatMessage c) | 217 | // ChatTypeEnum chatType, ChatSourceType sourceType, string message) |
218 | // { | ||
219 | // // iterate over message | ||
220 | // if (message.Length >= 1000) // libomv limit | ||
221 | // message = message.Substring(0, 1000); | ||
222 | |||
223 | // foreach (Scene s in m_scenes) | ||
224 | // { | ||
225 | // s.ForEachScenePresence(delegate(ScenePresence presence) | ||
226 | // { | ||
227 | // TrySendChatMessage(presence, pos, regionPos, uuid, name, | ||
228 | // chatType, message, sourceType); | ||
229 | // }); | ||
230 | // } | ||
231 | // } | ||
232 | |||
233 | |||
234 | public virtual void OnChatBroadcast(Object sender, OSChatMessage c) | ||
189 | { | 235 | { |
190 | // We only want to relay stuff on channel 0 and on the debug channel | 236 | // unless the chat to be broadcast is of type Region, we |
191 | if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return; | 237 | // drop it if its channel is neither 0 nor DEBUG_CHANNEL |
238 | if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL && c.Type != ChatTypeEnum.Region) return; | ||
192 | 239 | ||
240 | ChatTypeEnum cType = c.Type; | ||
193 | if (c.Channel == DEBUG_CHANNEL) | 241 | if (c.Channel == DEBUG_CHANNEL) |
194 | c.Type = ChatTypeEnum.DebugChannel; | 242 | cType = ChatTypeEnum.DebugChannel; |
243 | |||
244 | if (cType == ChatTypeEnum.Region) | ||
245 | cType = ChatTypeEnum.Say; | ||
195 | 246 | ||
196 | if (c.Message.Length > 1100) | 247 | if (c.Message.Length > 1100) |
197 | c.Message = c.Message.Substring(0, 1000); | 248 | c.Message = c.Message.Substring(0, 1000); |
@@ -199,6 +250,15 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
199 | // broadcast chat works by redistributing every incoming chat | 250 | // broadcast chat works by redistributing every incoming chat |
200 | // message to each avatar in the scene. | 251 | // message to each avatar in the scene. |
201 | Vector3 pos = new Vector3(128, 128, 30); | 252 | Vector3 pos = new Vector3(128, 128, 30); |
253 | |||
254 | UUID fromID = UUID.Zero; | ||
255 | ChatSourceType sourceType = ChatSourceType.Object; | ||
256 | if (null != c.Sender) | ||
257 | { | ||
258 | fromID = c.Sender.AgentId; | ||
259 | sourceType = ChatSourceType.Agent; | ||
260 | } | ||
261 | |||
202 | ((Scene)c.Scene).ForEachScenePresence( | 262 | ((Scene)c.Scene).ForEachScenePresence( |
203 | delegate(ScenePresence presence) | 263 | delegate(ScenePresence presence) |
204 | { | 264 | { |
@@ -214,29 +274,15 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat | |||
214 | (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId)) | 274 | (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId)) |
215 | return; | 275 | return; |
216 | 276 | ||
217 | if (null == c.SenderObject) | 277 | client.SendChatMessage(c.Message, (byte)cType, pos, c.From, fromID, |
218 | { | 278 | (byte)sourceType, (byte)ChatAudibleLevel.Fully); |
219 | // chat from agent (avatar) | ||
220 | client.SendChatMessage(c.Message, (byte)c.Type, | ||
221 | pos, c.From, UUID.Zero, | ||
222 | (byte)ChatSourceType.Agent, | ||
223 | (byte)ChatAudibleLevel.Fully); | ||
224 | } | ||
225 | else | ||
226 | { | ||
227 | // chat from object | ||
228 | client.SendChatMessage(c.Message, (byte)c.Type, | ||
229 | pos, c.From, UUID.Zero, | ||
230 | (byte)ChatSourceType.Object, | ||
231 | (byte)ChatAudibleLevel.Fully); | ||
232 | } | ||
233 | }); | 279 | }); |
234 | } | 280 | } |
235 | 281 | ||
236 | 282 | ||
237 | private void TrySendChatMessage(ScenePresence presence, Vector3 fromPos, Vector3 regionPos, | 283 | protected virtual void TrySendChatMessage(ScenePresence presence, Vector3 fromPos, Vector3 regionPos, |
238 | UUID fromAgentID, string fromName, ChatTypeEnum type, | 284 | UUID fromAgentID, string fromName, ChatTypeEnum type, |
239 | string message, ChatSourceType src) | 285 | string message, ChatSourceType src) |
240 | { | 286 | { |
241 | // don't send stuff to child agents | 287 | // don't send stuff to child agents |
242 | if (presence.IsChildAgent) return; | 288 | if (presence.IsChildAgent) return; |
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 | } |
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs new file mode 100644 index 0000000..0eb303c --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs | |||
@@ -0,0 +1,710 @@ | |||
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 OpenMetaverse; | ||
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 IRCConnector | ||
45 | { | ||
46 | #region ErrorReplies enum | ||
47 | |||
48 | public enum ErrorReplies | ||
49 | { | ||
50 | NotRegistered = 451, // ":You have not registered" | ||
51 | NicknameInUse = 433 // "<nick> :Nickname is already in use" | ||
52 | } | ||
53 | |||
54 | #endregion | ||
55 | |||
56 | #region Replies enum | ||
57 | |||
58 | public enum Replies | ||
59 | { | ||
60 | MotdStart = 375, // ":- <server> Message of the day - " | ||
61 | Motd = 372, // ":- <text>" | ||
62 | EndOfMotd = 376 // ":End of /MOTD command" | ||
63 | } | ||
64 | |||
65 | #endregion | ||
66 | |||
67 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
68 | |||
69 | private Thread m_listener = null; | ||
70 | private Thread m_watchdog = null; | ||
71 | private Thread m_pinger = null; | ||
72 | |||
73 | private bool m_randomizeNick = true; | ||
74 | |||
75 | public string m_baseNick = null; | ||
76 | private string m_nick = null; | ||
77 | public string Nick | ||
78 | { | ||
79 | get { return m_baseNick; } | ||
80 | set { m_baseNick = value; } | ||
81 | } | ||
82 | |||
83 | private bool m_enabled = false; | ||
84 | public bool Enabled | ||
85 | { | ||
86 | get { return m_enabled; } | ||
87 | } | ||
88 | |||
89 | private bool m_connected = false; | ||
90 | public bool Connected | ||
91 | { | ||
92 | get { return m_connected; } | ||
93 | } | ||
94 | |||
95 | private string m_ircChannel; | ||
96 | public string IrcChannel | ||
97 | { | ||
98 | get { return m_ircChannel; } | ||
99 | |||
100 | set { m_ircChannel = value; } | ||
101 | } | ||
102 | |||
103 | private bool m_relayPrivateChannels = false; | ||
104 | public bool RelayPrivateChannels | ||
105 | { | ||
106 | get { return m_relayPrivateChannels; } | ||
107 | set { m_relayPrivateChannels = value; } | ||
108 | } | ||
109 | |||
110 | private int m_relayChannel = 0; | ||
111 | public int RelayChannel | ||
112 | { | ||
113 | get { return m_relayChannel; } | ||
114 | set { m_relayChannel = value; } | ||
115 | } | ||
116 | |||
117 | private bool m_clientReporting = true; | ||
118 | public bool ClientReporting | ||
119 | { | ||
120 | get { return m_clientReporting; } | ||
121 | set { m_clientReporting = value; } | ||
122 | } | ||
123 | |||
124 | private uint m_port = 6667; | ||
125 | public uint Port | ||
126 | { | ||
127 | get { return m_port; } | ||
128 | set { m_port = value; } | ||
129 | } | ||
130 | |||
131 | private string m_server = null; | ||
132 | public string Server | ||
133 | { | ||
134 | get { return m_server; } | ||
135 | set { m_server = value; } | ||
136 | } | ||
137 | |||
138 | public string m_accessPassword = "badkitty"; | ||
139 | |||
140 | private string m_privmsgformat = "PRIVMSG {0} :<{1} in {2}>: {3}"; | ||
141 | private StreamReader m_reader; | ||
142 | private List<Scene> m_scenes = new List<Scene>(); | ||
143 | |||
144 | private NetworkStream m_stream = null; | ||
145 | internal object m_syncConnect = new object(); | ||
146 | private TcpClient m_tcp; | ||
147 | private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot"; | ||
148 | private StreamWriter m_writer; | ||
149 | |||
150 | |||
151 | public IRCConnector(IConfigSource config) | ||
152 | { | ||
153 | m_tcp = null; | ||
154 | m_writer = null; | ||
155 | m_reader = null; | ||
156 | |||
157 | // configuration in OpenSim.ini | ||
158 | // [IRC] | ||
159 | // server = chat.freenode.net | ||
160 | // nick = OSimBot_mysim | ||
161 | // nicknum = true | ||
162 | // ;nicknum set to true appends a 2 digit random number to the nick | ||
163 | // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot | ||
164 | // ; username is the IRC command line sent | ||
165 | // ; USER <irc_user> <visible=8,invisible=0> * : <IRC_realname> | ||
166 | // channel = #opensim-regions | ||
167 | // port = 6667 | ||
168 | // ;MSGformat fields : 0=botnick, 1=user, 2=region, 3=message | ||
169 | // ;for <bot>:<user in region> :<message> | ||
170 | // ;msgformat = "PRIVMSG {0} :<{1} in {2}>: {3}" | ||
171 | // ;for <bot>:<message> - <user of region> : | ||
172 | // ;msgformat = "PRIVMSG {0} : {3} - {1} of {2}" | ||
173 | // ;for <bot>:<message> - from <user> : | ||
174 | // ;msgformat = "PRIVMSG {0} : {3} - from {1}" | ||
175 | // Traps I/O disconnects so it does not crash the sim | ||
176 | // Trys to reconnect if disconnected and someone says something | ||
177 | // Tells IRC server "QUIT" when doing a close (just to be nice) | ||
178 | // Default port back to 6667 | ||
179 | |||
180 | try | ||
181 | { | ||
182 | m_server = config.Configs["IRC"].GetString("server"); | ||
183 | m_baseNick = config.Configs["IRC"].GetString("nick", "OSimBot"); | ||
184 | |||
185 | m_randomizeNick = config.Configs["IRC"].GetBoolean("randomize_nick", m_randomizeNick); | ||
186 | m_randomizeNick = config.Configs["IRC"].GetBoolean("nicknum", m_randomizeNick); // compat | ||
187 | m_ircChannel = config.Configs["IRC"].GetString("channel"); | ||
188 | m_port = (uint)config.Configs["IRC"].GetInt("port", (int)m_port); | ||
189 | m_user = config.Configs["IRC"].GetString("username", m_user); | ||
190 | m_privmsgformat = config.Configs["IRC"].GetString("msgformat", m_privmsgformat); | ||
191 | // m_commandChannel = config.Configs["IRC"].GetInt("commandchannel", m_commandChannel); | ||
192 | |||
193 | // m_verbosity = config.Configs["IRC"].GetInt("verbosity", m_verbosity); | ||
194 | m_clientReporting = config.Configs["IRC"].GetInt("verbosity", 2) > 0; | ||
195 | m_clientReporting = config.Configs["IRC"].GetBoolean("report_clients", m_clientReporting); | ||
196 | |||
197 | // m_messageOutChannel = config.Configs["IRC"].GetInt("outchannel", m_messageOutChannel); | ||
198 | // m_messageInChannel = config.Configs["IRC"].GetInt("inchannel", m_messageInChannel); | ||
199 | m_relayPrivateChannels = config.Configs["IRC"].GetBoolean("relay_private_channels", m_relayPrivateChannels); | ||
200 | m_relayPrivateChannels = config.Configs["IRC"].GetBoolean("useworldcomm", m_relayPrivateChannels); //compat | ||
201 | m_relayChannel = config.Configs["IRC"].GetInt("relay_private_channel_in", m_relayChannel); | ||
202 | m_relayChannel = config.Configs["IRC"].GetInt("inchannel", m_relayChannel); | ||
203 | m_accessPassword = config.Configs["IRC"].GetString("access_password",m_accessPassword); | ||
204 | // m_useWorldComm = config.Configs["IRC"].GetBoolean("useworldcomm", m_useWorldComm); | ||
205 | |||
206 | if (m_server != null && m_baseNick != null && m_ircChannel != null) | ||
207 | { | ||
208 | if (m_randomizeNick) | ||
209 | { | ||
210 | m_nick = m_baseNick + Util.RandomClass.Next(1, 99); | ||
211 | } | ||
212 | m_enabled = true; | ||
213 | } | ||
214 | } | ||
215 | catch (Exception ex) | ||
216 | { | ||
217 | m_log.Error("[IRCConnector]: Incomplete IRC configuration, skipping IRC bridge configuration"); | ||
218 | m_log.DebugFormat("[IRCConnector] Incomplete IRC configuration: {0}", ex.ToString()); | ||
219 | } | ||
220 | |||
221 | if (null == m_watchdog) | ||
222 | { | ||
223 | m_watchdog = new Thread(WatchdogRun); | ||
224 | m_watchdog.Name = "IRCWatchdog"; | ||
225 | m_watchdog.IsBackground = true; | ||
226 | } | ||
227 | } | ||
228 | |||
229 | public void Start() | ||
230 | { | ||
231 | if (!m_watchdog.IsAlive) | ||
232 | { | ||
233 | m_watchdog.Start(); | ||
234 | ThreadTracker.Add(m_watchdog); | ||
235 | } | ||
236 | } | ||
237 | |||
238 | public void AddScene(Scene scene) | ||
239 | { | ||
240 | lock(m_syncConnect) m_scenes.Add(scene); | ||
241 | } | ||
242 | |||
243 | public bool Connect() | ||
244 | { | ||
245 | lock (m_syncConnect) | ||
246 | { | ||
247 | try | ||
248 | { | ||
249 | if (m_connected) return true; | ||
250 | |||
251 | m_tcp = new TcpClient(m_server, (int)m_port); | ||
252 | m_stream = m_tcp.GetStream(); | ||
253 | m_reader = new StreamReader(m_stream); | ||
254 | m_writer = new StreamWriter(m_stream); | ||
255 | |||
256 | m_log.DebugFormat("[IRCConnector]: Connected to {0}:{1}", m_server, m_port); | ||
257 | |||
258 | m_pinger = new Thread(new ThreadStart(PingRun)); | ||
259 | m_pinger.Name = "PingSenderThread"; | ||
260 | m_pinger.IsBackground = true; | ||
261 | m_pinger.Start(); | ||
262 | ThreadTracker.Add(m_pinger); | ||
263 | |||
264 | m_listener = new Thread(new ThreadStart(ListenerRun)); | ||
265 | m_listener.Name = "IRCConnectorListenerThread"; | ||
266 | m_listener.IsBackground = true; | ||
267 | m_listener.Start(); | ||
268 | ThreadTracker.Add(m_listener); | ||
269 | |||
270 | m_writer.WriteLine(m_user); | ||
271 | m_writer.Flush(); | ||
272 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
273 | m_writer.Flush(); | ||
274 | m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel)); | ||
275 | m_writer.Flush(); | ||
276 | m_log.Info("[IRCConnector]: Connection fully established"); | ||
277 | m_connected = true; | ||
278 | } | ||
279 | catch (Exception e) | ||
280 | { | ||
281 | m_log.ErrorFormat("[IRCConnector] cannot connect to {0}:{1}: {2}", | ||
282 | m_server, m_port, e.Message); | ||
283 | } | ||
284 | m_log.Debug("[IRCConnector] Connected"); | ||
285 | return m_connected; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | public void Reconnect() | ||
290 | { | ||
291 | m_connected = false; | ||
292 | try | ||
293 | { | ||
294 | m_listener.Abort(); | ||
295 | m_pinger.Abort(); | ||
296 | |||
297 | m_writer.Close(); | ||
298 | m_reader.Close(); | ||
299 | |||
300 | m_tcp.Close(); | ||
301 | } | ||
302 | catch (Exception) | ||
303 | { | ||
304 | } | ||
305 | |||
306 | if (m_enabled) | ||
307 | { | ||
308 | Connect(); | ||
309 | } | ||
310 | } | ||
311 | |||
312 | public void PrivMsg(string from, string region, string msg) | ||
313 | { | ||
314 | m_log.DebugFormat("[IRCConnector] Sending message to IRC from {0}: {1}", from, msg); | ||
315 | |||
316 | // One message to the IRC server | ||
317 | try | ||
318 | { | ||
319 | m_writer.WriteLine(m_privmsgformat, m_ircChannel, from, region, msg); | ||
320 | m_writer.Flush(); | ||
321 | m_log.InfoFormat("[IRCConnector]: PrivMsg {0} in {1}: {2}", from, region, msg); | ||
322 | } | ||
323 | catch (IOException) | ||
324 | { | ||
325 | m_log.Error("[IRCConnector]: Disconnected from IRC server.(PrivMsg)"); | ||
326 | Reconnect(); | ||
327 | } | ||
328 | catch (Exception ex) | ||
329 | { | ||
330 | m_log.ErrorFormat("[IRCConnector]: PrivMsg exception trap: {0}", ex.ToString()); | ||
331 | } | ||
332 | } | ||
333 | |||
334 | public void Send(string msg) | ||
335 | { | ||
336 | try | ||
337 | { | ||
338 | m_writer.WriteLine(msg); | ||
339 | m_writer.Flush(); | ||
340 | m_log.Info("IRC: Sent command string: " + msg); | ||
341 | } | ||
342 | catch (IOException) | ||
343 | { | ||
344 | m_log.Error("[IRCConnector]: Disconnected from IRC server.(PrivMsg)"); | ||
345 | Reconnect(); | ||
346 | } | ||
347 | catch (Exception ex) | ||
348 | { | ||
349 | m_log.ErrorFormat("[IRCConnector]: PrivMsg exception trap: {0}", ex.ToString()); | ||
350 | } | ||
351 | |||
352 | } | ||
353 | |||
354 | |||
355 | private Dictionary<string, string> ExtractMsg(string input) | ||
356 | { | ||
357 | //examines IRC commands and extracts any private messages | ||
358 | // which will then be reboadcast in the Sim | ||
359 | |||
360 | m_log.Info("[IRCConnector]: ExtractMsg: " + input); | ||
361 | Dictionary<string, string> result = null; | ||
362 | string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)"; | ||
363 | Regex RE = new Regex(regex, RegexOptions.Multiline); | ||
364 | MatchCollection matches = RE.Matches(input); | ||
365 | |||
366 | // Get some direct matches $1 $4 is a | ||
367 | if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5)) | ||
368 | { | ||
369 | // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count); | ||
370 | // if (matches.Count > 0) | ||
371 | // { | ||
372 | // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count); | ||
373 | // } | ||
374 | return null; | ||
375 | } | ||
376 | |||
377 | result = new Dictionary<string, string>(); | ||
378 | result.Add("nick", matches[0].Groups[1].Value); | ||
379 | result.Add("user", matches[0].Groups[2].Value); | ||
380 | result.Add("channel", matches[0].Groups[3].Value); | ||
381 | result.Add("msg", matches[0].Groups[4].Value); | ||
382 | |||
383 | return result; | ||
384 | } | ||
385 | |||
386 | public void PingRun() | ||
387 | { | ||
388 | // IRC keep alive thread | ||
389 | // send PING ever 15 seconds | ||
390 | while (m_enabled) | ||
391 | { | ||
392 | try | ||
393 | { | ||
394 | if (m_connected == true) | ||
395 | { | ||
396 | m_writer.WriteLine(String.Format("PING :{0}", m_server)); | ||
397 | m_writer.Flush(); | ||
398 | Thread.Sleep(15000); | ||
399 | } | ||
400 | } | ||
401 | catch (IOException) | ||
402 | { | ||
403 | if (m_enabled) | ||
404 | { | ||
405 | m_log.Error("[IRCConnector]: Disconnected from IRC server.(PingRun)"); | ||
406 | Reconnect(); | ||
407 | } | ||
408 | } | ||
409 | catch (Exception ex) | ||
410 | { | ||
411 | m_log.ErrorFormat("[IRCConnector]: PingRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
412 | } | ||
413 | } | ||
414 | } | ||
415 | |||
416 | static private Vector3 pos = new Vector3(128, 128, 20); | ||
417 | public void ListenerRun() | ||
418 | { | ||
419 | string inputLine; | ||
420 | |||
421 | while (m_enabled) | ||
422 | { | ||
423 | try | ||
424 | { | ||
425 | while ((m_connected) && ((inputLine = m_reader.ReadLine()) != null)) | ||
426 | { | ||
427 | // m_log.Info("[IRCConnector]: " + inputLine); | ||
428 | |||
429 | if (inputLine.Contains(m_ircChannel)) | ||
430 | { | ||
431 | Dictionary<string, string> data = ExtractMsg(inputLine); | ||
432 | // Any chat ??? | ||
433 | if (data != null) | ||
434 | { | ||
435 | OSChatMessage c = new OSChatMessage(); | ||
436 | c.Message = data["msg"]; | ||
437 | c.Type = ChatTypeEnum.Region; | ||
438 | c.Position = pos; | ||
439 | c.Channel = m_relayPrivateChannels ? m_relayChannel : 0; | ||
440 | c.From = data["nick"]; | ||
441 | c.Sender = null; | ||
442 | c.SenderUUID = UUID.Zero; | ||
443 | |||
444 | // is message "\001ACTION foo | ||
445 | // bar\001"? -> "/me foo bar" | ||
446 | if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION")) | ||
447 | c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9)); | ||
448 | |||
449 | m_log.DebugFormat("[IRCConnector] ListenerRun from: {0}, {1}", c.From, c.Message); | ||
450 | |||
451 | foreach (Scene scene in m_scenes) | ||
452 | { | ||
453 | c.Scene = scene; | ||
454 | scene.EventManager.TriggerOnChatBroadcast(this, c); | ||
455 | } | ||
456 | } | ||
457 | |||
458 | Thread.Sleep(150); | ||
459 | continue; | ||
460 | } | ||
461 | |||
462 | ProcessIRCCommand(inputLine); | ||
463 | Thread.Sleep(150); | ||
464 | } | ||
465 | } | ||
466 | catch (IOException) | ||
467 | { | ||
468 | if (m_enabled) | ||
469 | { | ||
470 | m_log.Error("[IRCConnector]: ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)"); | ||
471 | Reconnect(); | ||
472 | } | ||
473 | } | ||
474 | catch (Exception ex) | ||
475 | { | ||
476 | m_log.ErrorFormat("[IRCConnector]: ListenerRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
477 | } | ||
478 | } | ||
479 | } | ||
480 | |||
481 | public void BroadcastSim(string sender, string format, params string[] args) | ||
482 | { | ||
483 | try | ||
484 | { | ||
485 | OSChatMessage c = new OSChatMessage(); | ||
486 | c.From = sender; | ||
487 | c.Message = String.Format(format, args); | ||
488 | c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say; | ||
489 | c.Channel = m_relayPrivateChannels ? m_relayChannel : 0; | ||
490 | c.Position = new Vector3(128, 128, 20); | ||
491 | c.Sender = null; | ||
492 | c.SenderUUID = UUID.Zero; | ||
493 | |||
494 | m_log.DebugFormat("[IRCConnector] BroadcastSim from {0}: {1}", c.From, c.Message); | ||
495 | |||
496 | foreach (Scene scene in m_scenes) | ||
497 | { | ||
498 | c.Scene = scene; | ||
499 | scene.EventManager.TriggerOnChatBroadcast(this, c); | ||
500 | // // m_scene.EventManager.TriggerOnChatFromWorld(this, c); | ||
501 | // IWorldComm wComm = m_scene.RequestModuleInterface<IWorldComm>(); | ||
502 | // wComm.DeliverMessage(ChatTypeEnum.Region, m_messageInChannel, sender, UUID.Zero, c.Message); | ||
503 | // //IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>(); | ||
504 | // //wComm.DeliverMessage(ChatTypeEnum.Region, channelID, m_host.Name, m_host.UUID, text); | ||
505 | |||
506 | } | ||
507 | } | ||
508 | catch (Exception ex) // IRC gate should not crash Sim | ||
509 | { | ||
510 | m_log.ErrorFormat("[IRCConnector]: BroadcastSim Exception Trap: {0}\n{1}", ex.ToString(), ex.StackTrace); | ||
511 | } | ||
512 | } | ||
513 | |||
514 | public void ProcessIRCCommand(string command) | ||
515 | { | ||
516 | // m_log.Debug("[IRCConnector]: ProcessIRCCommand:" + command); | ||
517 | |||
518 | string[] commArgs = new string[command.Split(' ').Length]; | ||
519 | string c_server = m_server; | ||
520 | |||
521 | commArgs = command.Split(' '); | ||
522 | if (commArgs[0].Substring(0, 1) == ":") | ||
523 | { | ||
524 | commArgs[0] = commArgs[0].Remove(0, 1); | ||
525 | } | ||
526 | |||
527 | if (commArgs[1] == "002") | ||
528 | { | ||
529 | // fetch the correct servername | ||
530 | // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/... | ||
531 | // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch | ||
532 | |||
533 | c_server = (commArgs[6].Split('['))[0]; | ||
534 | m_server = c_server; | ||
535 | } | ||
536 | |||
537 | if (commArgs[0] == "ERROR") | ||
538 | { | ||
539 | m_log.ErrorFormat("[IRCConnector]: IRC SERVER ERROR: {0}", command); | ||
540 | } | ||
541 | |||
542 | if (commArgs[0] == "PING") | ||
543 | { | ||
544 | string p_reply = ""; | ||
545 | |||
546 | for (int i = 1; i < commArgs.Length; i++) | ||
547 | { | ||
548 | p_reply += commArgs[i] + " "; | ||
549 | } | ||
550 | |||
551 | m_writer.WriteLine(String.Format("PONG {0}", p_reply)); | ||
552 | m_writer.Flush(); | ||
553 | } | ||
554 | else if (commArgs[0] == c_server) | ||
555 | { | ||
556 | // server message | ||
557 | try | ||
558 | { | ||
559 | Int32 commandCode = Int32.Parse(commArgs[1]); | ||
560 | switch (commandCode) | ||
561 | { | ||
562 | case (int)ErrorReplies.NicknameInUse: | ||
563 | // Gen a new name | ||
564 | m_nick = m_baseNick + Util.RandomClass.Next(1, 99); | ||
565 | m_log.ErrorFormat("[IRCConnector]: IRC SERVER reports NicknameInUse, trying {0}", m_nick); | ||
566 | // Retry | ||
567 | m_writer.WriteLine(String.Format("NICK {0}", m_nick)); | ||
568 | m_writer.Flush(); | ||
569 | m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel)); | ||
570 | m_writer.Flush(); | ||
571 | break; | ||
572 | case (int)ErrorReplies.NotRegistered: | ||
573 | break; | ||
574 | case (int)Replies.EndOfMotd: | ||
575 | break; | ||
576 | } | ||
577 | } | ||
578 | catch (Exception) | ||
579 | { | ||
580 | } | ||
581 | } | ||
582 | else | ||
583 | { | ||
584 | // Normal message | ||
585 | string commAct = commArgs[1]; | ||
586 | switch (commAct) | ||
587 | { | ||
588 | case "JOIN": | ||
589 | eventIrcJoin(commArgs); | ||
590 | break; | ||
591 | case "PART": | ||
592 | eventIrcPart(commArgs); | ||
593 | break; | ||
594 | case "MODE": | ||
595 | eventIrcMode(commArgs); | ||
596 | break; | ||
597 | case "NICK": | ||
598 | eventIrcNickChange(commArgs); | ||
599 | break; | ||
600 | case "KICK": | ||
601 | eventIrcKick(commArgs); | ||
602 | break; | ||
603 | case "QUIT": | ||
604 | eventIrcQuit(commArgs); | ||
605 | break; | ||
606 | case "PONG": | ||
607 | break; // that's nice | ||
608 | } | ||
609 | } | ||
610 | } | ||
611 | |||
612 | public void eventIrcJoin(string[] commArgs) | ||
613 | { | ||
614 | string IrcChannel = commArgs[2]; | ||
615 | if (IrcChannel.StartsWith(":")) | ||
616 | IrcChannel = IrcChannel.Substring(1); | ||
617 | string IrcUser = commArgs[0].Split('!')[0]; | ||
618 | if (m_clientReporting) | ||
619 | BroadcastSim(IrcUser, "/me joins {0}", IrcChannel); | ||
620 | } | ||
621 | |||
622 | public void eventIrcPart(string[] commArgs) | ||
623 | { | ||
624 | string IrcChannel = commArgs[2]; | ||
625 | string IrcUser = commArgs[0].Split('!')[0]; | ||
626 | if (m_clientReporting) | ||
627 | BroadcastSim(IrcUser, "/me parts {0}", IrcChannel); | ||
628 | } | ||
629 | |||
630 | public void eventIrcMode(string[] commArgs) | ||
631 | { | ||
632 | string UserMode = ""; | ||
633 | for (int i = 3; i < commArgs.Length; i++) | ||
634 | { | ||
635 | UserMode += commArgs[i] + " "; | ||
636 | } | ||
637 | |||
638 | if (UserMode.Substring(0, 1) == ":") | ||
639 | { | ||
640 | UserMode = UserMode.Remove(0, 1); | ||
641 | } | ||
642 | } | ||
643 | |||
644 | public void eventIrcNickChange(string[] commArgs) | ||
645 | { | ||
646 | string UserOldNick = commArgs[0].Split('!')[0]; | ||
647 | string UserNewNick = commArgs[2].Remove(0, 1); | ||
648 | if (m_clientReporting) | ||
649 | BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick); | ||
650 | } | ||
651 | |||
652 | public void eventIrcKick(string[] commArgs) | ||
653 | { | ||
654 | string UserKicker = commArgs[0].Split('!')[0]; | ||
655 | string UserKicked = commArgs[3]; | ||
656 | string IrcChannel = commArgs[2]; | ||
657 | string KickMessage = ""; | ||
658 | for (int i = 4; i < commArgs.Length; i++) | ||
659 | { | ||
660 | KickMessage += commArgs[i] + " "; | ||
661 | } | ||
662 | if (m_clientReporting) | ||
663 | BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage); | ||
664 | if (UserKicked == m_nick) | ||
665 | { | ||
666 | BroadcastSim(m_nick, "Hey, that was me!!!"); | ||
667 | } | ||
668 | } | ||
669 | |||
670 | public void eventIrcQuit(string[] commArgs) | ||
671 | { | ||
672 | string IrcUser = commArgs[0].Split('!')[0]; | ||
673 | string QuitMessage = ""; | ||
674 | |||
675 | for (int i = 2; i < commArgs.Length; i++) | ||
676 | { | ||
677 | QuitMessage += commArgs[i] + " "; | ||
678 | } | ||
679 | if (m_clientReporting) | ||
680 | BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage); | ||
681 | } | ||
682 | |||
683 | public void Close() | ||
684 | { | ||
685 | m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing", | ||
686 | m_nick, m_ircChannel, m_server)); | ||
687 | m_writer.Flush(); | ||
688 | |||
689 | m_connected = false; | ||
690 | m_enabled = false; | ||
691 | |||
692 | //listener.Abort(); | ||
693 | //pingSender.Abort(); | ||
694 | |||
695 | m_writer.Close(); | ||
696 | m_reader.Close(); | ||
697 | m_stream.Close(); | ||
698 | m_tcp.Close(); | ||
699 | } | ||
700 | |||
701 | protected void WatchdogRun() | ||
702 | { | ||
703 | while (m_enabled) | ||
704 | { | ||
705 | if (!m_connected) Connect(); | ||
706 | Thread.Sleep(15000); | ||
707 | } | ||
708 | } | ||
709 | } | ||
710 | } \ No newline at end of file | ||