OpenClonk
C4Network2IRC.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 #include "C4Include.h"
18 #include "network/C4Network2IRC.h"
19 
20 #include "C4Version.h"
21 #include "game/C4Application.h"
22 #include "gui/C4Gui.h" // for clearly visi
24 
25 // Helper for IRC command parameter parsing
26 StdStrBuf ircExtractPar(const char **ppPar)
27 {
28  // No parameter left?
29  if (!ppPar || !*ppPar || !**ppPar)
30  return StdStrBuf("");
31  // Last parameter?
32  StdStrBuf Result;
33  if (**ppPar == ':')
34  {
35  // Reference everything after the double-colon
36  Result.Ref(*ppPar + 1);
37  *ppPar = nullptr;
38  }
39  else
40  {
41  // Copy until next space (or end of string)
42  Result.CopyUntil(*ppPar, ' ');
43  // Go over parameters
44  *ppPar += Result.getLength();
45  if (**ppPar == ' ')
46  (*ppPar)++;
47  else
48  *ppPar = nullptr;
49  }
50  // Done
51  return Result;
52 }
53 
54 // *** C4Network2IRCUser
55 
57  : Name(szName)
58 {
59 
60 }
61 
62 // *** C4Network2IRCChannel
63 
65  : Name(szName), pUsers(nullptr), fReceivingUsers(false)
66 {
67 
68 }
69 
71 {
72  ClearUsers();
73 }
74 
76 {
77  for (C4Network2IRCUser *pUser = pUsers; pUser; pUser = pUser->Next)
78  if (SEqual(pUser->getName(), szName))
79  return pUser;
80  return nullptr;
81 }
82 
83 void C4Network2IRCChannel::OnUsers(const char *szUsers, const char *szPrefixes)
84 {
85  // Find actual prefixes
86  szPrefixes = SSearch(szPrefixes, ")");
87  // Reconstructs the list
88  if (!fReceivingUsers)
89  ClearUsers();
90  while (szUsers && *szUsers)
91  {
92  // Get user name
93  StdStrBuf PrefixedName = ircExtractPar(&szUsers);
94  // Remove prefix(es)
95  const char *szName = PrefixedName.getData();
96  if (szPrefixes)
97  while (strchr(szPrefixes, *szName))
98  szName++;
99  // Copy prefix
100  StdStrBuf Prefix;
101  Prefix.Copy(PrefixedName.getData(), szName - PrefixedName.getData());
102  // Add user
103  AddUser(szName)->SetPrefix(Prefix.getData());
104  }
105  // Set flag the user list won't get cleared again until OnUsersEnd is called
106  fReceivingUsers = true;
107 }
108 
109 void C4Network2IRCChannel::OnUsersEnd()
110 {
111  // Reset flag
112  fReceivingUsers = false;
113 }
114 
115 void C4Network2IRCChannel::OnTopic(const char *szTopic)
116 {
117  Topic = szTopic;
118 }
119 
120 void C4Network2IRCChannel::OnKick(const char *szUser, const char *szComment)
121 {
122  // Remove named user from channel list
123  C4Network2IRCUser *pUser = getUser(szUser);
124  if (pUser) DeleteUser(pUser);
125 }
126 
127 void C4Network2IRCChannel::OnPart(const char *szUser, const char *szComment)
128 {
129  // Remove named user from channel list
130  C4Network2IRCUser *pUser = getUser(szUser);
131  if (pUser) DeleteUser(pUser);
132 }
133 
134 void C4Network2IRCChannel::OnJoin(const char *szUser)
135 {
136  // Add user (do not set prefix)
137  if (!getUser(szUser))
138  AddUser(szUser);
139 }
140 
141 C4Network2IRCUser *C4Network2IRCChannel::AddUser(const char *szName)
142 {
143  // Check if the user already exists
144  C4Network2IRCUser *pUser = getUser(szName);
145  if (pUser) return pUser;
146  // Add to list
147  pUser = new C4Network2IRCUser(szName);
148  pUser->Next = pUsers;
149  pUsers = pUser;
150  return pUser;
151 }
152 
153 void C4Network2IRCChannel::DeleteUser(C4Network2IRCUser *pUser)
154 {
155  // Unlink
156  if (pUser == pUsers)
157  pUsers = pUser->Next;
158  else
159  {
160  C4Network2IRCUser *pPrev = pUsers;
161  while (pPrev && pPrev->Next != pUser)
162  pPrev = pPrev->Next;
163  if (pPrev)
164  pPrev->Next = pUser->Next;
165  }
166  // Delete
167  delete pUser;
168 }
169 
170 void C4Network2IRCChannel::ClearUsers()
171 {
172  while (pUsers)
173  DeleteUser(pUsers);
174 }
175 
176 
177 // *** C4Network2IRCClient
178 // Created statically in C4Application.cpp, refer by &Application.IRCClient
179 
181 
183 {
184  Close();
185 }
186 
187 void C4Network2IRCClient::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf)
188 {
189  // Enlarge buffer
190  int iSize = rPacket.getSize(),
191  iPos = rOutBuf.getSize();
192  rOutBuf.Grow(iSize + 2);
193  // Write packet
194  rOutBuf.Write(rPacket, iPos);
195  // Terminate
196  uint8_t *pPos = getMBufPtr<uint8_t>(rOutBuf, iPos + iSize);
197  *pPos = '\r'; *(pPos + 1) = '\n';
198 }
199 
200 size_t C4Network2IRCClient::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr)
201 {
202  // Find line separation
203  const char *pSep = reinterpret_cast<const char *>(memchr(rInBuf.getData(), '\n', rInBuf.getSize()));
204  if (!pSep)
205  return 0;
206  // Check if it's actually correct separation (rarely the case)
207  int iSize = pSep - getBufPtr<char>(rInBuf) + 1,
208  iLength = iSize - 1;
209  if (iLength && *(pSep - 1) == '\r')
210  iLength--;
211  // Copy the line
212  StdStrBuf Buf; Buf.Copy(getBufPtr<char>(rInBuf), iLength);
213  // Ignore prefix
214  const char *pMsg = Buf.getData();
215  StdStrBuf Prefix;
216  if (*pMsg == ':')
217  {
218  Prefix.CopyUntil(pMsg + 1, ' ');
219  pMsg += Prefix.getLength() + 1;
220  }
221  // Strip whitespace
222  while (*pMsg == ' ')
223  pMsg++;
224  // Ignore empty message
225  if (!*pMsg)
226  return iSize;
227  // Get command
228  StdStrBuf Cmd; Cmd.CopyUntil(pMsg, ' ');
229  // Precess command
230  const char *szParameters = SSearch(pMsg, " ");
231  OnCommand(Prefix.getData(), Cmd.getData(), szParameters ? szParameters : "");
232  // Consume the line
233  return iSize;
234 }
235 
236 bool C4Network2IRCClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO)
237 {
238  // Security checks
239  if (!fConnecting || fConnected || AddrConnect != ServerAddr) return false;
240  CStdLock Lock(&CSec);
241  // Save connection data
242  fConnected = true;
243  fConnecting = false;
244  C4Network2IRCClient::PeerAddr = AddrPeer;
245  // Send welcome message
246  if (!Password.isNull())
247  Send("PASS", Password.getData());
248  Send("NICK", Nick.getData());
249  Send("USER", FormatString("clonk x x :%s", RealName.getLength() ? RealName.getData() : " ").getData());
250  // Okay
251  return true;
252 }
253 
254 void C4Network2IRCClient::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason)
255 {
256  fConnected = false;
257  // Show a message with the reason
258  PushMessage(MSG_Status, "", Nick.getData(), FormatString(LoadResStr("IDS_MSG_DISCONNECTEDFROMSERVER"), szReason).getData());
259 }
260 
261 void C4Network2IRCClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
262 {
263  // Won't get called
264 }
265 
267 {
268  return pChannels;
269 }
270 
272 {
273  return pPrevChan ? pPrevChan->Next : pChannels;
274 }
275 
277 {
278  for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
279  if (SEqualNoCase(pChan->getName(), szName))
280  return pChan;
281  return nullptr;
282 }
283 
285 {
286  // Clear log
287  while (iLogLength)
288  PopMessage();
289 }
290 
292 {
293  // set read marker to last message
294  pLogLastRead = pLogEnd;
295  iUnreadLogLength = 0;
296  // message buffer is smaller for messages already read: Remove old ones
297  while (iLogLength > C4NetIRCMaxReadLogLength)
298  PopMessage();
299 }
300 
301 bool C4Network2IRCClient::Connect(const char *szServer, const char *szNick, const char *szRealName, const char *szPassword, const char *szAutoJoin)
302 {
303  // Already connected? Close connection
304  if (fConnecting || fConnected)
305  Close();
306  // Initialize
308  if (!Init())
309  return false;
310  // Resolve address
311  ServerAddr.SetAddress(StdStrBuf(szServer));
312  if (ServerAddr.IsNull())
313  { SetError("Could no resolve server address!"); return false; }
314  ServerAddr.SetDefaultPort(6666);
315  // Set connection data
316  Nick = szNick; RealName = szRealName;
317  Password = szPassword; AutoJoin = szAutoJoin;
318  // Truncate password
319  if (Password.getLength() > 31)
320  Password.SetLength(31);
321  // Start connecting
322  if (!C4NetIOTCP::Connect(ServerAddr))
323  return false;
324  // Reset status data
325  Prefixes = "(ov)@+";
326  // Okay, let's wait for the connection.
327  fConnecting = true;
328  return true;
329 }
330 
332 {
333  // Close network
335  // Save & Clear channels
336  if(pChannels) // Don't override empty
337  {
338  // It's somewhat weird to loop backward through a singly linked list, but it's necessary to keep the order
339  StdStrBuf chanstr;
340  C4Network2IRCChannel * pChan = pChannels;
341  while(pChan->Next)
342  pChan = pChan->Next;
343  chanstr.Append(pChan->getName());
344  while (pChan != pChannels)
345  {
346  C4Network2IRCChannel * pChanPrev = pChannels;
347  while(pChanPrev->Next != pChan)
348  pChanPrev = pChanPrev->Next;
349  pChan = pChanPrev;
350  chanstr.Append(",");
351  chanstr.Append(pChan->getName());
352  }
353  strncpy(Config.IRC.Channel, chanstr.getData(), sizeof(Config.IRC.Channel)-1);
354  }
355  while (pChannels)
356  DeleteChannel(pChannels);
357  // Clear log
358  ClearMessageLog();
359  // Reset flags
360  fConnected = fConnecting = false;
361  return true;
362 }
363 
364 bool C4Network2IRCClient::Send(const char *szCommand, const char *szParameters)
365 {
366  if (!fConnected)
367  { SetError("not connected"); return false; }
368  // Create message
369  StdStrBuf Msg;
370  if (szParameters)
371  Msg.Format("%s %s", szCommand, szParameters);
372  else
373  Msg.Ref(szCommand);
374  // Send
375  return C4NetIOTCP::Send(C4NetIOPacket(Msg.getData(), Msg.getLength(), false, PeerAddr));
376 }
377 
378 bool C4Network2IRCClient::Quit(const char *szReason)
379 {
380  if (!Send("QUIT", FormatString(":%s", szReason).getData()))
381  return false;
382  // Must be last message
383  return Close();
384 }
385 
386 bool C4Network2IRCClient::Join(const char *szChannel)
387 {
388  // Newbie limitation: can only join channels beginning with #clonk
390  if (!SWildcardMatchEx(szChannel, "#*clonk*"))
391  {
392  const char* message = LoadResStr("IDS_ERR_CHANNELNOTALLOWED");
393  PushMessage(MSG_Status, "", "", message);
394  SetError("Joining this channel not allowed");
395  Application.InteractiveThread.ThreadPostAsync<bool>(std::bind(&C4GUI::Screen::ShowMessage, ::pGUI, message, LoadResStr("IDS_DLG_CHAT"), C4GUI::Ico_Error, static_cast<int32_t* const &>(nullptr)));
396  return false;
397  }
398  return Send("JOIN", szChannel);
399 }
400 
401 bool C4Network2IRCClient::Part(const char *szChannel)
402 {
403  return Send("PART", szChannel);
404 }
405 
406 bool C4Network2IRCClient::Message(const char *szTarget, const char *szText)
407 {
408  if (!Send("PRIVMSG", FormatString("%s :%s", szTarget, szText).getData()))
409  return false;
410  PushMessage(MSG_Message, Nick.getData(), szTarget, szText);
411  return true;
412 }
413 
414 bool C4Network2IRCClient::Notice(const char *szTarget, const char *szText)
415 {
416  if (!Send("NOTICE", FormatString("%s :%s", szTarget, szText).getData()))
417  return false;
418  PushMessage(MSG_Notice, Nick.getData(), szTarget, szText);
419  return true;
420 }
421 
422 bool C4Network2IRCClient::Action(const char *szTarget, const char *szText)
423 {
424  if (!Send("PRIVMSG", FormatString("%s :\1ACTION %s\1", szTarget, szText).getData()))
425  return false;
426  PushMessage(MSG_Action, Nick.getData(), szTarget, szText);
427  return true;
428 }
429 
430 bool C4Network2IRCClient::ChangeNick(const char *szNewNick)
431 {
432  return Send("NICK", szNewNick);
433 }
434 
435 bool C4Network2IRCClient::RegisterNick(const char *szPassword, const char *szMail)
436 {
437  return Send("PRIVMSG", FormatString("NickServ :REGISTER %s %s", szPassword, szMail).getData());
438 }
439 
440 void C4Network2IRCClient::OnCommand(const char *szSender, const char *szCommand, const char *szParameters)
441 {
442  CStdLock Lock(&CSec);
443  // Numeric command?
444  if (isdigit((unsigned char)*szCommand) && SLen(szCommand) == 3)
445  {
446  OnNumericCommand(szSender, atoi(szCommand), szParameters);
447  return;
448  }
449  // Sender's nick
450  StdStrBuf SenderNick;
451  if (szSender) SenderNick.CopyUntil(szSender, '!');
452  // Ping?
453  if (SEqualNoCase(szCommand, "PING"))
454  Send("PONG", szParameters);
455  // Message?
456  if (SEqualNoCase(szCommand, "NOTICE") || SEqualNoCase(szCommand, "PRIVMSG"))
457  {
458  // Get target
459  StdStrBuf Target = ircExtractPar(&szParameters);
460  // Get text
461  StdStrBuf Text = ircExtractPar(&szParameters);
462  // Process message
463  OnMessage(SEqualNoCase(szCommand, "NOTICE"), szSender, Target.getData(), Text.getData());
464  }
465  // Channel join?
466  if (SEqualNoCase(szCommand, "JOIN"))
467  {
468  // Get channel
469  StdStrBuf Channel = ircExtractPar(&szParameters);
470  C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
471  // Add user
472  pChan->OnJoin(SenderNick.getData());
473  // Myself?
474  if (SenderNick == Nick)
475  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_YOUHAVEJOINEDCHANNEL"), Channel.getData()).getData());
476  else
477  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_HASJOINEDTHECHANNEL"), SenderNick.getData()).getData());
478  }
479  // Channel part?
480  if (SEqualNoCase(szCommand, "PART"))
481  {
482  // Get channel
483  StdStrBuf Channel = ircExtractPar(&szParameters);
484  C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
485  // Get message
486  StdStrBuf Comment = ircExtractPar(&szParameters);
487  // Remove user
488  pChan->OnPart(SenderNick.getData(), Comment.getData());
489  // Myself?
490  if (SenderNick == Nick)
491  {
492  DeleteChannel(pChan);
493  PushMessage(MSG_Status, szSender, Nick.getData(), FormatString(LoadResStr("IDS_MSG_YOUHAVELEFTCHANNEL"), Channel.getData(), Comment.getData()).getData());
494  }
495  else
496  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_HASLEFTTHECHANNEL"), SenderNick.getData(), Comment.getData()).getData());
497  }
498  // Kick?
499  if (SEqualNoCase(szCommand, "KICK"))
500  {
501  // Get channel
502  StdStrBuf Channel = ircExtractPar(&szParameters);
503  C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
504  // Get kicked user
505  StdStrBuf Kicked = ircExtractPar(&szParameters);
506  // Get message
507  StdStrBuf Comment = ircExtractPar(&szParameters);
508  // Remove user
509  pChan->OnKick(Kicked.getData(), Comment.getData());
510  // Myself?
511  if (Kicked == Nick)
512  {
513  DeleteChannel(pChan);
514  PushMessage(MSG_Status, szSender, Nick.getData(), FormatString(LoadResStr("IDS_MSG_YOUWEREKICKEDFROMCHANNEL"), Channel.getData(), Comment.getData()).getData());
515  }
516  else
517  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_WASKICKEDFROMTHECHANNEL"), Kicked.getData(), Comment.getData()).getData());
518  }
519  // Quit?
520  if (SEqualNoCase(szCommand, "QUIT"))
521  {
522  // Get comment
523  StdStrBuf Comment = ircExtractPar(&szParameters);
524  // Format status message
525  StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_HASDISCONNECTED"), SenderNick.getData(), Comment.getData());
526  // Remove him from all channels
527  for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
528  if (pChan->getUser(SenderNick.getData()))
529  {
530  pChan->OnPart(SenderNick.getData(), "Quit");
531  PushMessage(MSG_Status, szSender, pChan->getName(), Message.getData());
532  }
533  }
534  // Topic change?
535  if (SEqualNoCase(szCommand, "TOPIC"))
536  {
537  // Get channel and topic
538  StdStrBuf Channel = ircExtractPar(&szParameters);
539  StdStrBuf Topic = ircExtractPar(&szParameters);
540  // Set topic
541  AddChannel(Channel.getData())->OnTopic(Topic.getData());
542  // Message
543  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_CHANGESTHETOPICTO"), SenderNick.getData(), Topic.getData()).getData());
544  }
545  // Mode?
546  if (SEqualNoCase(szCommand, "MODE"))
547  {
548  // Get all data
549  StdStrBuf Channel = ircExtractPar(&szParameters);
550  StdStrBuf Flags = ircExtractPar(&szParameters);
551  StdStrBuf What = ircExtractPar(&szParameters);
552  // Make sure it's a channel
553  C4Network2IRCChannel *pChan = getChannel(Channel.getData());
554  if (pChan)
555  // Ask for names, because user prefixes might be out of sync
556  Send("NAMES", Channel.getData());
557  // Show Message
558  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_SETSMODE"), SenderNick.getData(), Flags.getData(), What.getData()).getData());
559  }
560  // Error?
561  if (SEqualNoCase(szCommand, "ERROR"))
562  {
563  // Get message
564  StdStrBuf Message = ircExtractPar(&szParameters);
565  // Push it
566  PushMessage(MSG_Server, szSender, Nick.getData(), Message.getData());
567  }
568  // Nickchange?
569  if (SEqualNoCase(szCommand, "NICK"))
570  {
571  // Get new nick
572  StdStrBuf NewNick = ircExtractPar(&szParameters);
573  // Format status message
574  StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_ISNOWKNOWNAS"), SenderNick.getData(), NewNick.getData());
575  // Rename on all channels
576  for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
577  if (pChan->getUser(SenderNick.getData()))
578  {
579  pChan->OnPart(SenderNick.getData(), "Nickchange");
580  pChan->OnJoin(NewNick.getData());
581  PushMessage(MSG_Status, szSender, pChan->getName(), Message.getData());
582  }
583  // Self?
584  if (SenderNick == Nick)
585  Nick = NewNick;
586  }
587 }
588 
589 void C4Network2IRCClient::OnNumericCommand(const char *szSender, int iCommand, const char *szParameters)
590 {
591  bool fShowMessage = true;
592  // Get target
593  StdStrBuf Target = ircExtractPar(&szParameters);
594  // Handle command
595  switch (iCommand)
596  {
597 
598  case 433: // Nickname already in use
599  {
600  StdStrBuf DesiredNick = ircExtractPar(&szParameters);
601  // Automatically try to choose a new one
602  DesiredNick.AppendChar('_');
603  Send("NICK", DesiredNick.getData());
604  break;
605  }
606 
607  case 376: // End of MOTD
608  case 422: // MOTD missing
609  // Let's take this a sign that the connection is established.
610  OnConnected();
611  break;
612 
613  case 331: // No topic set
614  case 332: // Topic notify / change
615  {
616  // Get Channel name and topic
617  StdStrBuf Channel = ircExtractPar(&szParameters);
618  StdStrBuf Topic = (iCommand == 332 ? ircExtractPar(&szParameters) : StdStrBuf(""));
619  // Set it
620  AddChannel(Channel.getData())->OnTopic(Topic.getData());
621  // Log
622  if (Topic.getLength())
623  PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_TOPICIN"), Channel.getData(), Topic.getData()).getData());
624  }
625  break;
626 
627  case 333: // Last topic change
628  fShowMessage = false; // ignore
629  break;
630 
631  case 353: // Names in channel
632  {
633  // Get Channel name and name list
634  StdStrBuf Junk = ircExtractPar(&szParameters); // ??!
635  StdStrBuf Channel = ircExtractPar(&szParameters);
636  StdStrBuf Names = ircExtractPar(&szParameters);
637  // Set it
638  AddChannel(Channel.getData())->OnUsers(Names.getData(), Prefixes.getData());
639  fShowMessage = false;
640  }
641  break;
642 
643  case 366: // End of names list
644  {
645  // Get Channel name
646  StdStrBuf Channel = ircExtractPar(&szParameters);
647  // Finish
648  AddChannel(Channel.getData())->OnUsersEnd();
649  fShowMessage = false;
650  // Notify
651  if (pNotify) pNotify->PushEvent(Ev_IRC_Message, this);
652  }
653  break;
654 
655  case 4: // Server version
656  fShowMessage = false; // ignore
657  break;
658 
659  case 5: // Server support string
660  {
661  while (szParameters && *szParameters)
662  {
663  // Get support-token.
664  StdStrBuf Token = ircExtractPar(&szParameters);
665  StdStrBuf Parameter; Parameter.CopyUntil(Token.getData(), '=');
666  // Check if it's interesting and safe data if so.
667  if (SEqualNoCase(Parameter.getData(), "PREFIX"))
668  Prefixes.Copy(SSearch(Token.getData(), "="));
669  }
670  fShowMessage = false;
671  }
672  break;
673 
674  }
675  // Show embedded message, if any?
676  if (fShowMessage)
677  {
678  // Check if first parameter is some sort of channel name
679  C4Network2IRCChannel *pChannel = nullptr;
680  if (szParameters && *szParameters && *szParameters != ':')
681  pChannel = getChannel(ircExtractPar(&szParameters).getData());
682  // Go over other parameters
683  const char *pMsg = szParameters;
684  while (pMsg && *pMsg && *pMsg != ':')
685  pMsg = SSearch(pMsg, " ");
686  // Show it
687  if (pMsg && *pMsg)
688  {
689  if (!pChannel)
690  PushMessage(MSG_Server, szSender, Nick.getData(), pMsg + 1);
691  else
692  PushMessage(MSG_Status, szSender, pChannel->getName(), pMsg + 1);
693  }
694  }
695 }
696 
697 void C4Network2IRCClient::OnConnected()
698 {
699 
700  // Set flag
701  fConnected = true;
702 
703  // Try to join channel(s)
704  if (AutoJoin.getLength())
705  Join(AutoJoin.getData());
706 
707 }
708 
709 void C4Network2IRCClient::OnMessage(bool fNotice, const char *szSender, const char *szTarget, const char *szText)
710 {
711  // CTCP tagged data?
712  const char X_DELIM = '\001';
713  if (szText[0] == X_DELIM)
714  {
715  // Process messages (it's very rarely more than one, but the spec allows it)
716  const char *pMsg = szText + 1;
717  while (*pMsg)
718  {
719  // Find end
720  const char *pEnd = strchr(pMsg, X_DELIM);
721  if (!pEnd) pEnd = pMsg + SLen(pMsg);
722  // Copy CTCP query/reply, get tag
723  StdStrBuf CTCP; CTCP.Copy(pMsg, pEnd - pMsg);
724  StdStrBuf Tag; Tag.CopyUntil(CTCP.getData(), ' ');
725  const char *szData = SSearch(CTCP.getData(), " ");
726  StdStrBuf Sender; Sender.CopyUntil(szSender, '!');
727  // Process
728  if (SEqualNoCase(Tag.getData(), "ACTION"))
729  PushMessage(MSG_Action, szSender, szTarget, szData ? szData : "");
730  if (SEqualNoCase(Tag.getData(), "FINGER") && !fNotice)
731  {
732  StdStrBuf Answer;
733  Answer = LoadResStr("IDS_PRC_UNREGUSER"); //ToDo: Anser sth. else
734 
735  Send("NOTICE", FormatString("%s :%cFINGER %s%c",
736  Sender.getData(), X_DELIM,
737  Answer.getData(),
738  X_DELIM).getData());
739  }
740  if (SEqualNoCase(Tag.getData(), "VERSION") && !fNotice)
741  Send("NOTICE", FormatString("%s :%cVERSION " C4ENGINECAPTION ":" C4VERSION ":" C4_OS "%c",
742  Sender.getData(), X_DELIM, X_DELIM).getData());
743  if (SEqualNoCase(Tag.getData(), "PING") && !fNotice)
744  Send("NOTICE", FormatString("%s :%cPING %s%c",
745  Sender.getData(), X_DELIM, szData, X_DELIM).getData());
746  // Get next message
747  pMsg = pEnd;
748  if (*pMsg == X_DELIM) pMsg++;
749  }
750  }
751 
752  // Standard message (not CTCP tagged): Push
753  else
754  PushMessage(fNotice ? MSG_Notice : MSG_Message, szSender, szTarget, szText);
755 
756 }
757 
758 void C4Network2IRCClient::PopMessage()
759 {
760  if (!iLogLength) return;
761  // Unlink message
762  C4Network2IRCMessage *pMsg = pLog;
763  pLog = pMsg->Next;
764  if (!pLog) pLogEnd = nullptr;
765  if (pLogLastRead == pMsg) pLogLastRead = nullptr;
766  // Delete it
767  delete pMsg;
768  iLogLength--;
769  if (iUnreadLogLength > iLogLength) iUnreadLogLength = iLogLength;
770 }
771 
772 void C4Network2IRCClient::PushMessage(C4Network2IRCMessageType eType, const char *szSource, const char *szTarget, const char *szText)
773 {
774  // Create message
775  C4Network2IRCMessage *pMsg = new C4Network2IRCMessage(eType, szSource, szTarget, szText);
776  // Add to list
777  if (pLogEnd)
778  {
779  pLogEnd->Next = pMsg;
780  }
781  else
782  {
783  pLog = pMsg;
784  }
785  pLogEnd = pMsg;
786  // Count
787  iLogLength++;
788  iUnreadLogLength++;
789  while (iLogLength > C4NetIRCMaxLogLength)
790  PopMessage();
791  // Notify
792  if (pNotify)
793  pNotify->PushEvent(Ev_IRC_Message, this);
794 }
795 
796 C4Network2IRCChannel *C4Network2IRCClient::AddChannel(const char *szName)
797 {
798  // Already exists?
799  C4Network2IRCChannel *pChan = getChannel(szName);
800  if (pChan) return pChan;
801  // Create new, insert
802  pChan = new C4Network2IRCChannel(szName);
803  pChan->Next = pChannels;
804  pChannels = pChan;
805  return pChan;
806 }
807 
808 void C4Network2IRCClient::DeleteChannel(C4Network2IRCChannel *pChannel)
809 {
810  // Unlink
811  if (pChannel == pChannels)
812  pChannels = pChannel->Next;
813  else
814  {
815  C4Network2IRCChannel *pPrev = pChannels;
816  while (pPrev && pPrev->Next != pChannel)
817  pPrev = pPrev->Next;
818  if (pPrev)
819  pPrev->Next = pChannel->Next;
820  }
821  // Delete
822  delete pChannel;
823 }
C4Config Config
Definition: C4Config.cpp:930
C4Application Application
Definition: C4Globals.cpp:44
C4GUIScreen * pGUI
Definition: C4Gui.cpp:1191
@ Ev_IRC_Message
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
StdStrBuf ircExtractPar(const char **ppPar)
C4Network2IRCMessageType
Definition: C4Network2IRC.h:22
@ MSG_Notice
Definition: C4Network2IRC.h:26
@ MSG_Action
Definition: C4Network2IRC.h:27
@ MSG_Server
Definition: C4Network2IRC.h:23
@ MSG_Status
Definition: C4Network2IRC.h:24
@ MSG_Message
Definition: C4Network2IRC.h:25
const int C4NetIRCMaxReadLogLength
Definition: C4Network2IRC.h:31
const int C4NetIRCMaxLogLength
Definition: C4Network2IRC.h:30
#define C4_OS
bool SWildcardMatchEx(const char *szString, const char *szWildcard)
Definition: Standard.cpp:635
const char * SSearch(const char *szString, const char *szIndex)
Definition: Standard.cpp:369
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
size_t SLen(const char *sptr)
Definition: Standard.h:74
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
int iSize
Definition: TstC4NetIO.cpp:32
C4InteractiveThread InteractiveThread
Definition: C4Application.h:45
C4ConfigIRC IRC
Definition: C4Config.h:261
int32_t AllowAllChannels
Definition: C4Config.h:214
char Channel[CFG_MaxString+1]
Definition: C4Config.h:212
bool ShowMessage(const char *szMessage, const char *szCaption, Icons icoIcon, int32_t *piConfigDontShowAgainSetting=nullptr)
bool PushEvent(C4InteractiveEventType eEventType, void *pData=nullptr)
bool ThreadPostAsync(Functor function)
void SetError(const char *strnError, bool fSockErr=false)
Definition: C4NetIO.cpp:750
bool Send(const C4NetIOPacket &rPacket) override
Definition: C4NetIO.cpp:1310
bool Init(uint16_t iPort=addr_t::IPPORT_NONE) override
Definition: C4NetIO.cpp:811
void SetCallback(CBClass *pnCallback) override
Definition: C4NetIO.h:505
bool Close() override
Definition: C4NetIO.cpp:857
bool Connect(const addr_t &addr, std::unique_ptr< Socket > socket)
Definition: C4NetIO.cpp:1207
C4Network2IRCUser * getUser(const char *szName) const
C4Network2IRCChannel(const char *szName)
const char * getName() const
bool Send(const C4NetIOPacket &rPacket) override
Definition: C4NetIO.cpp:1310
bool Action(const char *szTarget, const char *szText)
bool Close() override
bool ChangeNick(const char *szNewNick)
bool RegisterNick(const char *szPassword, const char *szMail)
bool Notice(const char *szTarget, const char *szText)
bool Join(const char *szChannel)
C4Network2IRCChannel * getFirstChannel() const
~C4Network2IRCClient() override
C4Network2IRCChannel * getChannel(const char *szName) const
C4Network2IRCChannel * getNextChannel(C4Network2IRCChannel *pPrevChan) const
bool Message(const char *szTarget, const char *szText)
bool Quit(const char *szReason)
bool Part(const char *szChannel)
bool Connect(const addr_t &addr, std::unique_ptr< Socket > socket)
Definition: C4NetIO.cpp:1207
C4Network2IRCUser(const char *szName)
Definition: StdBuf.h:30
size_t getSize() const
Definition: StdBuf.h:101
void Grow(size_t iGrow)
Definition: StdBuf.h:171
void Write(const void *pnData, size_t inSize, size_t iAt=0)
Definition: StdBuf.h:153
const void * getData() const
Definition: StdBuf.h:99
void SetLength(size_t iLength)
Definition: StdBuf.h:509
void CopyUntil(const char *szString, char cUntil)
Definition: StdBuf.h:613
void Ref(const char *pnData)
Definition: StdBuf.h:455
const char * getData() const
Definition: StdBuf.h:442
bool isNull() const
Definition: StdBuf.h:441
void AppendChar(char cChar)
Definition: StdBuf.h:588
void Copy()
Definition: StdBuf.h:467
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
size_t getLength() const
Definition: StdBuf.h:445
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
@ Ico_Error
Definition: C4Gui.h:652
void SetAddress(const sockaddr *addr)
Definition: C4NetIO.cpp:364
void SetDefaultPort(uint16_t port)
Definition: C4NetIO.cpp:541