cl_irc.c

Go to the documentation of this file.
00001 
00006 /*
00007 All original material Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 Most of this stuff comes from Warsow
00010 
00011 This program is free software; you can redistribute it and/or
00012 modify it under the terms of the GNU General Public License
00013 as published by the Free Software Foundation; either version 2
00014 of the License, or (at your option) any later version.
00015 
00016 This program is distributed in the hope that it will be useful,
00017 but WITHOUT ANY WARRANTY; without even the implied warranty of
00018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00019 
00020 See the GNU General Public License for more details.
00021 
00022 You should have received a copy of the GNU General Public License
00023 along with this program; if not, write to the Free Software
00024 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00025 
00026 */
00027 
00028 #include "client.h"
00029 #include "cl_irc.h"
00030 #include "ui/ui_main.h"
00031 #include "ui/ui_nodes.h"
00032 #include "ui/ui_popup.h"
00033 #include "multiplayer/mp_chatmessages.h"
00034 
00035 #ifdef _WIN32
00036 #   include <winerror.h>
00037 #else
00038 #   include <netinet/in.h>
00039 #   include <arpa/inet.h>
00040 #   include <netdb.h>
00041 #   include <fcntl.h>
00042 #endif
00043 
00044 static cvar_t *irc_server;
00045 static cvar_t *irc_port;
00046 static cvar_t *irc_channel;
00047 static cvar_t *irc_nick;
00048 static cvar_t *irc_user;
00049 static cvar_t *irc_password;
00050 static cvar_t *irc_topic;
00051 static cvar_t *irc_defaultChannel;
00052 static cvar_t *irc_logConsole;
00053 static cvar_t *irc_showIfNotInMenu;
00054 /* menu cvar */
00055 static cvar_t *irc_send_buffer;
00056 
00057 static qboolean irc_connected;
00058 
00059 #define IRC_SEND_BUF_SIZE 512
00060 #define IRC_RECV_BUF_SIZE 1024
00061 
00062 typedef struct irc_user_s {
00063     char key[MAX_VAR];
00064     struct irc_user_s *next;
00065 } irc_user_t;
00066 
00067 typedef struct irc_channel_s {
00068     char name[MAX_VAR];
00069     char topic[256];
00070     int users;
00071     irc_user_t *user;
00072 } irc_channel_t;
00073 
00074 /* numeric commands as specified by RFC 1459 - Internet Relay Chat Protocol */
00075 typedef enum irc_numeric_e {
00076     /* command replies */
00077     RPL_WELCOME = 1,                /* ":Welcome to the Internet Relay Network <nick>!<user>@<host>" */
00078     RPL_YOURHOST = 2,               /* ":Your host is <servername>, running version <ver>" */
00079     RPL_CREATED = 3,                /* ":This server was created <date>" */
00080     RPL_MYINFO = 4,                 /* "<servername> <version> <available user modes> <available channel modes>" */
00081     RPL_ISUPPORT = 5,               /* "<nick> <parameter> * :are supported by this server" */
00082     RPL_HELLO = 20,                 /* ":Please wait while we process your connection" */
00083     RPL_NONE = 300,
00084     RPL_USERHOST = 302,             /* ":[<reply>{<space><reply>}]" */
00085     RPL_ISON = 303,                 /* ":[<nick> {<space><nick>}]" */
00086     RPL_AWAY = 301,                 /* "<nick> :<away message>" */
00087     RPL_UNAWAY = 305,               /* ":You are no longer marked as being away" */
00088     RPL_NOWAWAY = 306,              /* ":You have been marked as being away" */
00089     RPL_WHOISUSER = 311,            /* "<nick> <user> <host> * :<real name>" */
00090     RPL_WHOISSERVER = 312,          /* "<nick> <server> :<server info>" */
00091     RPL_WHOISOPERATOR = 313,        /* "<nick> :is an IRC operator" */
00092     RPL_WHOISIDLE = 317,            /* "<nick> <integer> :seconds idle" */
00093     RPL_ENDOFWHOIS = 318,           /* "<nick> :End of /WHOIS list" */
00094     RPL_WHOISCHANNELS = 319,        /* "<nick> :{[@|+]<channel><space>}" */
00095     RPL_WHOWASUSER = 314,           /* "<nick> <user> <host> * :<real name>" */
00096     RPL_ENDOFWHOWAS = 369,          /* "<nick> :End of WHOWAS" */
00097     RPL_WHOISACCOUNT = 330,         /* "<nick> <account> :is logged in as" */
00098 
00099     RPL_LISTSTART = 321,            /* "Channel :Users  Name" */
00100     RPL_LIST = 322,                 /* "<channel> <# visible> :<topic>" */
00101     RPL_LISTEND = 323,              /* ":End of /LIST" */
00102     RPL_CHANNELMODEIS = 324,        /* "<channel> <mode> <mode params>" */
00103     RPL_NOTOPIC = 331,              /* "<channel> :No topic is set" */
00104     RPL_TOPIC = 332,                /* "<channel> :<topic>" */
00105     RPL_TOPICWHOTIME = 333,         /* "<channel> <nick> <time>" */
00106     RPL_INVITING = 341,             /* "<channel> <nick>" */
00107     RPL_SUMMONING = 342,            /* "<user> :Summoning user to IRC" */
00108     RPL_VERSION = 351,              /* "<version>.<debuglevel> <server> :<comments>" */
00109     RPL_WHOREPLY = 352,             /* "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
00110     RPL_ENDOFWHO = 315,             /* "<name> :End of /WHO list" */
00111     RPL_NAMREPLY = 353,             /* "<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]" */
00112     RPL_ENDOFNAMES = 366,           /* "<channel> :End of /NAMES list" */
00113     RPL_LINKS = 364,                /* "<mask> <server> :<hopcount> <server info>" */
00114     RPL_ENDOFLINKS = 365,           /* "<mask> :End of /LINKS list" */
00115     RPL_BANLIST = 367,              /* "<channel> <banid>" */
00116     RPL_ENDOFBANLIST = 368,         /* "<channel> :End of channel ban list" */
00117     RPL_INFO = 371,                 /* ":<string>" */
00118     RPL_ENDOFINFO = 374,            /* ":End of /INFO list" */
00119     RPL_MOTDSTART = 375,            /* ":- <server> Message of the day - " */
00120     RPL_MOTD = 372,                 /* ":- <text>" */
00121     RPL_ENDOFMOTD = 376,            /* ":End of /MOTD command" */
00122     RPL_YOUREOPER = 381,            /* ":You are now an IRC operator" */
00123     RPL_REHASHING = 382,            /* "<config file> :Rehashing" */
00124     RPL_TIME = 391,                 /* "<server> :<string showing server's local time>" */
00125     RPL_USERSSTART = 392,           /* ":UserID   Terminal  Host" */
00126     RPL_USERS = 393,                /* ":%-8s %-9s %-8s" */
00127     RPL_ENDOFUSERS = 394,           /* ":End of users" */
00128     RPL_NOUSERS = 395,              /* ":Nobody logged in" */
00129     RPL_TRACELINK = 200,            /* "Link <version & debug level> <destination> <next server>" */
00130     RPL_TRACECONNECTING = 201,      /* "Try. <class> <server>" */
00131     RPL_TRACEHANDSHAKE = 202,       /* "H.S. <class> <server>" */
00132     RPL_TRACEUNKNOWN = 203,         /* "???? <class> [<client IP address in dot form>]" */
00133     RPL_TRACEOPERATOR = 204,        /* "Oper <class> <nick>" */
00134     RPL_TRACEUSER = 205,            /* "User <class> <nick>" */
00135     RPL_TRACESERVER = 206,          /* "Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>" */
00136     RPL_TRACENEWTYPE = 208,         /* "<newtype> 0 <client name>" */
00137     RPL_TRACELOG = 261,             /* "File <logfile> <debug level>" */
00138     RPL_STATSLINKINFO = 211,        /* "<linkname> <sendq> <sent messages> <sent bytes> <received messages> <received bytes> <time open>" */
00139     RPL_STATSCOMMANDS = 212,        /* "<command> <count>" */
00140     RPL_STATSCLINE = 213,           /* "C <host> * <name> <port> <class>" */
00141     RPL_STATSNLINE = 214,           /* "N <host> * <name> <port> <class>" */
00142     RPL_STATSILINE = 215,           /* "I <host> * <host> <port> <class>" */
00143     RPL_STATSKLINE = 216,           /* "K <host> * <username> <port> <class>" */
00144     RPL_STATSYLINE = 218,           /* "Y <class> <ping frequency> <connectfrequency> <max sendq>" */
00145     RPL_ENDOFSTATS = 219,           /* "<stats letter> :End of /STATS report" */
00146     RPL_STATSLLINE = 241,           /* "L <hostmask> * <servername> <maxdepth>" */
00147     RPL_STATSUPTIME = 242,          /* ":Server Up %d days %d:%02d:%02d" */
00148     RPL_STATSOLINE = 243,           /* "O <hostmask> * <name>" */
00149     RPL_STATSHLINE = 244,           /* "H <hostmask> * <servername>" */
00150     RPL_UMODEIS = 221,              /* "<user mode string>" */
00151     RPL_LUSERCLIENT = 251,          /* ":There are <integer> users and <integer> invisible on <integer> servers" */
00152     RPL_LUSEROP = 252,              /* "<integer> :operator(s) online" */
00153     RPL_LUSERUNKNOWN = 253,         /* "<integer> :unknown connection(s)" */
00154     RPL_LUSERCHANNELS = 254,        /* "<integer> :channels formed" */
00155     RPL_LUSERME = 255,              /* ":I have <integer> clients and <integer> servers" */
00156     RPL_ADMINME = 256,              /* "<server> :Administrative info" */
00157     RPL_ADMINLOC1 = 257,            /* ":<admin info>" */
00158     RPL_ADMINLOC2 = 258,            /* ":<admin info>" */
00159     RPL_ADMINEMAIL = 259,           /* ":<admin info>" */
00160     RPL_LOCALUSERS = 265,
00161     RPL_GLOBALUSERS = 266,
00162 
00163     /* error codes */
00164     ERR_NOSUCHNICK = 401,           /* "<nickname> :No such nick/channel" */
00165     ERR_NOSUCHSERVER = 402,         /* "<server name> :No such server" */
00166     ERR_NOSUCHCHANNEL = 403,        /* "<channel name> :No such channel" */
00167     ERR_CANNOTSENDTOCHAN = 404,     /* "<channel name> :Cannot send to channel" */
00168     ERR_TOOMANYCHANNELS = 405,      /* "<channel name> :You have joined too many channels" */
00169     ERR_WASNOSUCHNICK = 406,        /* "<nickname> :There was no such nickname" */
00170     ERR_TOOMANYTARGETS = 407,       /* "<target> :Duplicate recipients. No message delivered" */
00171     ERR_NOORIGIN = 409,             /* ":No origin specified" */
00172     ERR_NORECIPIENT = 411,          /* ":No recipient given (<command>)" */
00173     ERR_NOTEXTTOSEND = 412,         /* ":No text to send" */
00174     ERR_NOTOPLEVEL = 413,           /* "<mask> :No toplevel domain specified" */
00175     ERR_WILDTOPLEVEL = 414,         /* "<mask> :Wildcard in toplevel domain" */
00176     ERR_UNKNOWNCOMMAND = 421,       /* "<command> :Unknown command" */
00177     ERR_NOMOTD = 422,               /* ":MOTD File is missing" */
00178     ERR_NOADMININFO = 423,          /* "<server> :No administrative info available" */
00179     ERR_FILEERROR = 424,            /* ":File error doing <file op> on <file>" */
00180     ERR_NONICKNAMEGIVEN = 431,      /* ":No nickname given" */
00181     ERR_ERRONEUSNICKNAME = 432,     /* "<nick> :Erroneus nickname" */
00182     ERR_NICKNAMEINUSE = 433,        /* "<nick> :Nickname is already in use" */
00183     ERR_NICKCOLLISION = 436,        /* "<nick> :Nickname collision KILL" */
00184     ERR_BANNICKCHANGE = 437,
00185     ERR_NCHANGETOOFAST = 438,
00186     ERR_USERNOTINCHANNEL = 441,     /* "<nick> <channel> :They aren't on that channel" */
00187     ERR_NOTONCHANNEL = 442,         /* "<channel> :You're not on that channel" */
00188     ERR_USERONCHANNEL = 443,        /* "<user> <channel> :is already on channel" */
00189     ERR_NOLOGIN = 444,              /* "<user> :User not logged in" */
00190     ERR_SUMMONDISABLED = 445,       /* ":SUMMON has been disabled" */
00191     ERR_USERSDISABLED = 446,        /* ":USERS has been disabled" */
00192     ERR_NOTREGISTERED = 451,        /* ":You have not registered" */
00193     ERR_NEEDMOREPARAMS = 461,       /* "<command> :Not enough parameters" */
00194     ERR_ALREADYREGISTRED = 462,     /* ":You may not reregister" */
00195     ERR_NOPERMFORHOST = 463,        /* ":Your host isn't among the privileged" */
00196     ERR_PASSWDMISMATCH = 464,       /* ":Password incorrect" */
00197     ERR_YOUREBANNEDCREEP = 465,     /* ":You are banned from this server" */
00198     ERR_BADNAME = 468,              /* ":Your username is invalid" */
00199     ERR_KEYSET = 467,               /* "<channel> :Channel key already set" */
00200     ERR_CHANNELISFULL = 471,        /* "<channel> :Cannot join channel (+l)" */
00201     ERR_UNKNOWNMODE = 472,          /* "<char> :is unknown mode char to me" */
00202     ERR_INVITEONLYCHAN = 473,       /* "<channel> :Cannot join channel (+i)" */
00203     ERR_BANNEDFROMCHAN = 474,       /* "<channel> :Cannot join channel (+b)" */
00204     ERR_BADCHANNELKEY = 475,        /* "<channel> :Cannot join channel (+k)" */
00205     ERR_NOPRIVILEGES = 481,         /* ":Permission Denied- You're not an IRC operator" */
00206     ERR_CHANOPRIVSNEEDED = 482,     /* "<channel> :You're not channel operator" */
00207     ERR_CANTKILLSERVER = 483,       /* ":You cant kill a server!" */
00208     ERR_NOOPERHOST = 491,           /* ":No O-lines for your host" */
00209     ERR_UMODEUNKNOWNFLAG = 501,     /* ":Unknown MODE flag" */
00210     ERR_USERSDONTMATCH = 502,       /* ":Cant change mode for other users" */
00211     ERR_GHOSTEDCLIENT = 503,
00212     ERR_LAST_ERR_MSG = 504,
00213     ERR_SILELISTFULL = 511,
00214     ERR_NOSUCHGLINE = 512,
00215 /*  ERR_TOOMANYWATCH = 512,*/
00216     ERR_BADPING = 513,
00217     ERR_TOOMANYDCC = 514,
00218     ERR_LISTSYNTAX = 521,
00219     ERR_WHOSYNTAX = 522,
00220     ERR_WHOLIMEXCEED = 523
00221 } irc_numeric_t;
00222 
00223 typedef enum irc_command_type_e {
00224     IRC_COMMAND_NUMERIC,
00225     IRC_COMMAND_STRING
00226 } irc_command_type_t;
00227 
00228 typedef enum irc_nick_prefix_e {
00229     IRC_NICK_PREFIX_NONE = ' ',
00230     IRC_NICK_PREFIX_OP = '@',
00231     IRC_NICK_PREFIX_VOICE = '+'
00232 } irc_nick_prefix_t;
00233 
00234 /* commands can be numeric's or string */
00235 typedef struct irc_command_s {
00236     union {
00237         /* tagged union */
00238         irc_numeric_t   numeric;
00239         const char *    string;
00240     } id;
00241     irc_command_type_t  type;
00242 } irc_command_t;
00243 
00244 /* server <- client messages */
00245 typedef struct irc_server_msg_s {
00246     union {
00247         char string[IRC_SEND_BUF_SIZE];
00248         irc_numeric_t numeric;
00249     } id;
00250     irc_command_type_t type;
00251     char prefix[IRC_SEND_BUF_SIZE];
00252     char params[IRC_SEND_BUF_SIZE];
00253     char trailing[IRC_SEND_BUF_SIZE];
00254 } irc_server_msg_t;
00255 
00256 static struct net_stream *irc_stream;
00257 
00258 static const char IRC_QUIT_MSG[] = "ufoai.sf.net";
00259 static const char IRC_INVITE_FOR_A_GAME[] = "UFOAIINVITE;";
00260 
00261 static irc_channel_t ircChan;
00262 static irc_channel_t *chan;
00263 
00264 static char irc_buffer[4096];
00265 
00266 static void Irc_Logic_RemoveChannelName(irc_channel_t *channel, const char *nick);
00267 static void Irc_Logic_AddChannelName(irc_channel_t *channel, irc_nick_prefix_t prefix, const char *nick);
00268 static void Irc_Client_Names_f(void);
00269 static qboolean Irc_Client_Join(const char *channel, const char *password);
00270 static void Irc_Logic_Disconnect(const char *reason);
00271 
00272 static qboolean Irc_AppendToBuffer(const char* const msg, ...) __attribute__((format(__printf__, 1, 2)));
00273 static qboolean Irc_Proto_ParseServerMsg(const char *txt, size_t txt_len, irc_server_msg_t *msg);
00274 static qboolean Irc_Proto_Enqueue(const char *msg, size_t msg_len);
00275 
00276 static qboolean Irc_Net_Connect(const char *host, const char *port);
00277 static qboolean Irc_Net_Disconnect(void);
00278 static void Irc_Net_Send(const char *msg, size_t msg_len);
00279 
00280 static void Irc_Connect_f(void);
00281 static void Irc_Disconnect_f(void);
00282 static void Irc_Input_Deactivate_f(void);
00283 
00284 /*
00285 ===============================================================
00286 Common functions
00287 ===============================================================
00288 */
00289 
00290 static inline qboolean Irc_IsChannel (const char *target)
00291 {
00292     assert(target);
00293     return (target[0] == '#' || target[0] == '&');
00294 }
00295 
00296 static void Irc_ParseName (const char *mask, char *nick, size_t size, irc_nick_prefix_t *prefix)
00297 {
00298     const char *emph;
00299     if (mask[0] == IRC_NICK_PREFIX_OP || mask[0] == IRC_NICK_PREFIX_VOICE) {
00300         *prefix = (irc_nick_prefix_t) *mask;    /* read prefix */
00301         ++mask;                                 /* crop prefix from mask */
00302     } else
00303         *prefix = IRC_NICK_PREFIX_NONE;
00304     emph = strchr(mask, '!');
00305     if (emph) {
00306         size_t length = emph - mask;
00307         if (length >= size - 1)
00308             length = size - 1;
00309         /* complete hostmask, crop anything after ! */
00310         memcpy(nick, mask, length);
00311         nick[length] = '\0';
00312     } else
00313         /* just the nickname, use as is */
00314         Q_strncpyz(nick, mask, size);
00315 }
00316 
00317 /*
00318 ===============================================================
00319 Protocol functions
00320 ===============================================================
00321 */
00322 
00323 static cvar_t *irc_messageBucketSize;
00324 static cvar_t *irc_messageBucketBurst;
00325 static cvar_t *irc_characterBucketSize;
00326 static cvar_t *irc_characterBucketBurst;
00327 static cvar_t *irc_characterBucketRate;
00328 
00329 typedef struct irc_bucket_message_s {
00330     char *msg;
00331     size_t msg_len;
00332     struct irc_bucket_message_s *next;
00333 } irc_bucket_message_t;
00334 
00335 typedef struct irc_bucket_s {
00336     irc_bucket_message_t *first_msg;    
00337     unsigned int message_size;          
00338     unsigned int character_size;        
00339     int last_refill;                
00340     double character_token;
00341 } irc_bucket_t;
00342 
00343 static irc_bucket_t irc_bucket;
00344 
00348 static qboolean Irc_Proto_Connect (const char *host, const char *port)
00349 {
00350     const qboolean status = Irc_Net_Connect(host, port);
00351     if (!status) {
00352         irc_bucket.first_msg = NULL;
00353         irc_bucket.message_size = 0;
00354         irc_bucket.character_size = 0;
00355         irc_bucket.last_refill = CL_Milliseconds();
00356         irc_bucket.character_token = (double)irc_characterBucketBurst->value;
00357     }
00358     return status;
00359 }
00360 
00364 static qboolean Irc_Proto_Disconnect (void)
00365 {
00366     const qboolean status = Irc_Net_Disconnect();
00367     if (!status) {
00368         irc_bucket_message_t *msg = irc_bucket.first_msg;
00369         irc_bucket_message_t *prev;
00370         while (msg) {
00371             prev = msg;
00372             msg = msg->next;
00373             Mem_Free(prev->msg);
00374             Mem_Free(prev);
00375         }
00376         irc_bucket.first_msg = NULL;
00377         irc_bucket.message_size = 0;
00378         irc_bucket.character_size = 0;
00379     }
00380     return status;
00381 }
00382 
00386 static qboolean Irc_Proto_Quit (const char *quitmsg)
00387 {
00388     char msg[IRC_SEND_BUF_SIZE];
00389     const int msg_len = snprintf(msg, sizeof(msg) - 1, "QUIT %s\r\n", quitmsg);
00390     msg[sizeof(msg) - 1] = '\0';
00391     Irc_Net_Send(msg, msg_len); /* send immediately */
00392     return qfalse;
00393 }
00394 
00398 static qboolean Irc_Proto_Nick (const char *nick)
00399 {
00400     char msg[IRC_SEND_BUF_SIZE];
00401     const int msg_len = snprintf(msg, sizeof(msg) - 1, "NICK %s\r\n", nick);
00402     msg[sizeof(msg) - 1] = '\0';
00403     return Irc_Proto_Enqueue(msg, msg_len);
00404 }
00405 
00409 static qboolean Irc_Proto_User (const char *user, qboolean invisible, const char *name)
00410 {
00411     char msg[IRC_SEND_BUF_SIZE];
00412     const int msg_len = snprintf(msg, sizeof(msg) - 1, "USER %s %c * :%s\r\n", user, invisible ? '8' : '0', name);
00413     msg[sizeof(msg) - 1] = '\0';
00414     return Irc_Proto_Enqueue(msg, msg_len);
00415 }
00416 
00420 static qboolean Irc_Proto_Password (const char *password)
00421 {
00422     char msg[IRC_SEND_BUF_SIZE];
00423     const int msg_len = snprintf(msg, sizeof(msg) - 1, "PASS %s\r\n", password);
00424     msg[sizeof(msg) - 1] = '\0';
00425     return Irc_Proto_Enqueue(msg, msg_len);
00426 }
00427 
00431 static qboolean Irc_Proto_Join (const char *channel, const char *password)
00432 {
00433     char msg[IRC_SEND_BUF_SIZE];
00434     const int msg_len = password
00435         ? snprintf(msg, sizeof(msg) - 1, "JOIN %s %s\r\n", channel, password)
00436         : snprintf(msg, sizeof(msg) - 1, "JOIN %s\r\n", channel);
00437     msg[sizeof(msg) - 1] = '\0';
00438 
00439     /* only one channel allowed */
00440     if (chan) {
00441         Com_Printf("Already in a channel\n");
00442         return qfalse;
00443     }
00444 
00445     chan = &ircChan;
00446     memset(chan, 0, sizeof(*chan));
00447     Q_strncpyz(chan->name, channel, sizeof(chan->name));
00448     return Irc_Proto_Enqueue(msg, msg_len);
00449 }
00450 
00454 static qboolean Irc_Proto_Part (const char *channel)
00455 {
00456     char msg[IRC_SEND_BUF_SIZE];
00457     const int msg_len = snprintf(msg, sizeof(msg) - 1, "PART %s\r\n", channel);
00458     msg[sizeof(msg) - 1] = '\0';
00459     return Irc_Proto_Enqueue(msg, msg_len);
00460 }
00461 
00465 static qboolean Irc_Proto_Mode (const char *target, const char *modes, const char *params)
00466 {
00467     char msg[IRC_SEND_BUF_SIZE];
00468     const int msg_len = params
00469         ? snprintf(msg, sizeof(msg) - 1, "MODE %s %s %s\r\n", target, modes, params)
00470         : snprintf(msg, sizeof(msg) - 1, "MODE %s %s\r\n", target, modes);
00471     msg[sizeof(msg) - 1] = '\0';
00472     return Irc_Proto_Enqueue(msg, msg_len);
00473 }
00474 
00478 static qboolean Irc_Proto_Topic (const char *channel, const char *topic)
00479 {
00480     char msg[IRC_SEND_BUF_SIZE];
00481     const int msg_len = topic
00482         ? snprintf(msg, sizeof(msg) - 1, "TOPIC %s :%s\r\n", channel, topic)
00483         : snprintf(msg, sizeof(msg) - 1, "TOPIC %s\r\n", channel);
00484     msg[sizeof(msg) - 1] = '\0';
00485     return Irc_Proto_Enqueue(msg, msg_len);
00486 }
00487 
00493 static qboolean Irc_Proto_Msg (const char *target, const char *text)
00494 {
00495     if (*text == '/') {
00496         Com_DPrintf(DEBUG_CLIENT, "Don't send irc commands as PRIVMSG\n");
00497         Cbuf_AddText(va("%s\n", &text[1]));
00498         return qtrue;
00499     } else {
00500         char msg[IRC_SEND_BUF_SIZE];
00501         const int msg_len = snprintf(msg, sizeof(msg) - 1, "PRIVMSG %s :%s\r\n", target, text);
00502         msg[sizeof(msg) - 1] = '\0';
00503         return Irc_Proto_Enqueue(msg, msg_len);
00504     }
00505 }
00506 
00510 static qboolean Irc_Proto_Notice (const char *target, const char *text)
00511 {
00512     char msg[IRC_SEND_BUF_SIZE];
00513     const int msg_len = snprintf(msg, sizeof(msg) - 1, "NOTICE %s :%s\r\n", target, text);
00514     msg[sizeof(msg) - 1] = '\0';
00515     return Irc_Proto_Enqueue(msg, msg_len);
00516 }
00517 
00521 static void Irc_Proto_Pong (const char *nick, const char *server, const char *cookie)
00522 {
00523     char msg[IRC_SEND_BUF_SIZE];
00524     const int msg_len = cookie
00525         ? snprintf(msg, sizeof(msg) - 1, "PONG %s %s :%s\r\n", nick, server, cookie)
00526         : snprintf(msg, sizeof(msg) - 1, "PONG %s %s\r\n", nick, server);
00527     msg[sizeof(msg) - 1] = '\0';
00528     Irc_Net_Send(msg, msg_len); /* send immediately */
00529 }
00530 
00534 static qboolean Irc_Proto_Kick (const char *channel, const char *nick, const char *reason)
00535 {
00536     char msg[IRC_SEND_BUF_SIZE];
00537     const int msg_len = reason
00538         ? snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, reason)
00539         : snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, nick);
00540     msg[sizeof(msg) - 1] = '\0';
00541     return Irc_Proto_Enqueue(msg, msg_len);
00542 }
00543 
00547 static qboolean Irc_Proto_Who (const char *nick)
00548 {
00549     char msg[IRC_SEND_BUF_SIZE];
00550     const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHO %s\r\n", nick);
00551     msg[sizeof(msg) - 1] = '\0';
00552     return Irc_Proto_Enqueue(msg, msg_len);
00553 }
00554 
00558 static qboolean Irc_Proto_Whois (const char *nick)
00559 {
00560     char msg[IRC_SEND_BUF_SIZE];
00561     const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOIS %s\r\n", nick);
00562     msg[sizeof(msg) - 1] = '\0';
00563     return Irc_Proto_Enqueue(msg, msg_len);
00564 }
00565 
00569 static qboolean Irc_Proto_Whowas (const char *nick)
00570 {
00571     char msg[IRC_SEND_BUF_SIZE];
00572     const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOWAS %s\r\n", nick);
00573     msg[sizeof(msg) - 1] = '\0';
00574     return Irc_Proto_Enqueue(msg, msg_len);
00575 }
00576 
00580 static qboolean Irc_Proto_PollServerMsg (irc_server_msg_t *msg, qboolean *msg_complete)
00581 {
00582     static char buf[IRC_RECV_BUF_SIZE];
00583     static char *last = buf;
00584     int recvd;
00585     *msg_complete = qfalse;
00586     /* recv packet */
00587     recvd = NET_StreamDequeue(irc_stream, last, sizeof(buf) - (last - buf) - 1);
00588     if (recvd >= 0) {
00589         /* terminate buf string */
00590         const char * const begin = buf;
00591         last += recvd;
00592         *last = '\0';
00593         if (last != begin) {
00594             /* buffer not empty; */
00595             const char * const end = strstr(begin, "\r\n");
00596             if (end) {
00597                 /* complete command in buffer, parse */
00598                 const size_t cmd_len = end + 2 - begin;
00599                 if (!Irc_Proto_ParseServerMsg(begin, cmd_len, msg)) {
00600                     /* parsing successful */
00601                     /* move succeeding commands to begin of buffer */
00602                     memmove(buf, end + 2, sizeof(buf) - cmd_len);
00603                     last -= cmd_len;
00604                     *msg_complete = qtrue;
00605                 } else {
00606                     /* parsing failure, fatal */
00607                     Com_Printf("Received invalid packet from server\n");
00608                     return qtrue;
00609                 }
00610             }
00611         } else
00612             *msg_complete = qfalse;
00613         return qfalse;
00614     }
00615     return qtrue;
00616 }
00617 
00624 static qboolean Irc_AppendToBuffer (const char* const msg, ...)
00625 {
00626     char buf[IRC_RECV_BUF_SIZE];
00627     va_list ap;
00628     char appendString[2048];
00629 
00630     va_start(ap, msg);
00631     Q_vsnprintf(appendString, sizeof(appendString), msg, ap);
00632     va_end(ap);
00633 
00634     while (strlen(irc_buffer) + strlen(appendString) + 1 >= sizeof(irc_buffer)) {
00635         const char *n;
00636         if (!(n = strchr(irc_buffer, '\n'))) {
00637             irc_buffer[0] = '\0';
00638             break;
00639         }
00640         memmove(irc_buffer, n + 1, strlen(n));
00641     }
00642 
00643     Com_sprintf(buf, sizeof(buf), "%s\n", appendString);
00644     Q_strcat(irc_buffer, buf, sizeof(irc_buffer));
00645     if (irc_logConsole->integer)
00646         Com_Printf("IRC: %s\n", appendString);
00647 
00648     UI_RegisterText(TEXT_IRCCONTENT, irc_buffer);
00649     UI_TextScrollEnd("irc.irc_data");
00650 
00651     if (irc_showIfNotInMenu->integer && strcmp(UI_GetActiveWindowName(), "irc")) {
00652         S_StartLocalSample("misc/talk", SND_VOLUME_DEFAULT);
00653         MP_AddChatMessage(appendString);
00654         return qtrue;
00655     }
00656     return qfalse;
00657 }
00658 
00659 static void Irc_Client_CmdRplWhowasuser (const char *params, const char *trailing)
00660 {
00661     char buf[IRC_SEND_BUF_SIZE];
00662     const char *nick = "", *user = "", *host = "", *real_name = trailing;
00663     char *p;
00664     unsigned int i = 0;
00665 
00666     /* parse params "<nick> <user> <host> * :<real name>" */
00667     Q_strncpyz(buf, params, sizeof(buf));
00668     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00669         switch (i) {
00670         case 1:
00671             nick = p;
00672             break;
00673         case 2:
00674             user = p;
00675             break;
00676         case 3:
00677             host = p;
00678             break;
00679         }
00680     }
00681     Irc_AppendToBuffer("^B%s was %s@%s : %s", nick, user, host, real_name);
00682 }
00683 
00684 static inline void Irc_Client_CmdTopic (const char *prefix, const char *trailing)
00685 {
00686     Cvar_ForceSet("irc_topic", trailing);
00687 }
00688 
00689 static void Irc_Client_CmdRplTopic (const char *params, const char *trailing)
00690 {
00691     const char *channel = strchr(params, ' ');
00692     if (channel) {
00693         ++channel;
00694         Irc_Client_CmdTopic(params, trailing);
00695     }
00696 }
00697 
00698 static void Irc_Client_CmdRplWhoisuser (const char *params, const char *trailing)
00699 {
00700     char buf[IRC_SEND_BUF_SIZE];
00701     const char *nick = "", *user = "", *host = "", *real_name = trailing;
00702     char *p;
00703     unsigned int i = 0;
00704 
00705     /* parse params "<nick> <user> <host> * :<real name>" */
00706     strcpy(buf, params);
00707     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00708         switch (i) {
00709         case 1:
00710             nick = p;
00711             break;
00712         case 2:
00713             user = p;
00714             break;
00715         case 3:
00716             host = p;
00717             break;
00718         }
00719     }
00720     Irc_AppendToBuffer("^B%s is %s@%s : %s", nick, user, host, real_name);
00721 }
00722 
00723 static void Irc_Client_CmdRplWhoisserver (const char *params, const char *trailing)
00724 {
00725     char buf[IRC_SEND_BUF_SIZE];
00726     const char *nick = "", *server = "", *server_info = trailing;
00727     char *p;
00728     unsigned int i = 0;
00729 
00730     /* parse params "<nick> <server> :<server info>" */
00731     strcpy(buf, params);
00732     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00733         switch (i) {
00734         case 1:
00735             nick = p;
00736             break;
00737         case 2:
00738             server = p;
00739             break;
00740         }
00741     }
00742     Irc_AppendToBuffer("^B%s using %s : %s", nick, server, server_info);
00743 }
00744 
00745 static void Irc_Client_CmdRplWhoisaccount (const char *params, const char *trailing)
00746 {
00747     char buf[IRC_SEND_BUF_SIZE];
00748     const char *nick = "", *account = "";
00749     char *p;
00750     unsigned int i = 0;
00751 
00752     /* parse params "<nick> <account> :is logged in as" */
00753     strcpy(buf, params);
00754     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00755         switch (i) {
00756         case 1:
00757             nick = p;
00758             break;
00759         case 2:
00760             account = p;
00761             break;
00762         }
00763     }
00764     Irc_AppendToBuffer("^B%s %s %s", nick, trailing, account);
00765 }
00766 
00767 static void Irc_Client_CmdRplWhoisidle (const char *params, const char *trailing)
00768 {
00769     char buf[IRC_SEND_BUF_SIZE];
00770     const char *nick = "", *idle = "";
00771     char *p;
00772     unsigned int i = 0;
00773 
00774     /* parse params "<nick> <integer> :seconds idle" */
00775     strcpy(buf, params);
00776     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00777         switch (i) {
00778         case 1:
00779             nick = p;
00780             break;
00781         case 2:
00782             idle = p;
00783             break;
00784         }
00785     }
00786     Irc_AppendToBuffer("^B%s is %s %s", nick, idle, trailing);
00787 }
00788 
00789 static void Irc_Client_CmdRplWhoreply (const char *params, const char *trailing)
00790 {
00791     char buf[IRC_SEND_BUF_SIZE];
00792     const char *channel = "", *user = "", *host = "", *server = "", *nick = "", *hg = "";
00793     char *p;
00794     unsigned int i = 0;
00795 
00796     /* parse params "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
00797     strcpy(buf, params);
00798     for (p = strtok(buf, " "); p; p = strtok(NULL, " "), ++i) {
00799         switch (i) {
00800         case 0:
00801             channel = p;
00802             break;
00803         case 1:
00804             user = p;
00805             break;
00806         case 2:
00807             host = p;
00808             break;
00809         case 3:
00810             server = p;
00811             break;
00812         case 4:
00813             nick = p;
00814             break;
00815         case 5:
00816             hg = p;
00817             break;
00818         }
00819     }
00820     Irc_AppendToBuffer("%s %s %s %s %s %s : %s", channel, user, host, server, nick, hg, trailing);
00821 }
00822 
00823 static void Irc_Client_CmdMode (const char *prefix, const char *params, const char *trailing)
00824 {
00825     char nick[MAX_VAR];
00826     irc_nick_prefix_t p;
00827     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00828     Irc_AppendToBuffer("^B%s sets mode %s", nick, params);
00829 }
00830 
00831 static void Irc_Client_CmdJoin (const char *prefix, const char *params, const char *trailing)
00832 {
00833     char nick[MAX_VAR];
00834     irc_nick_prefix_t p;
00835     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00836     Irc_AppendToBuffer("^BJoined: %s", nick);
00837     Irc_Logic_AddChannelName(chan, p, nick);
00838 }
00839 
00840 static void Irc_Client_CmdPart (const char *prefix, const char *trailing)
00841 {
00842     char nick[MAX_VAR];
00843     irc_nick_prefix_t p;
00844     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00845     Irc_AppendToBuffer("^BLeft: %s (%s)", nick, prefix);
00846     Irc_Logic_RemoveChannelName(chan, nick);
00847 }
00848 
00849 static void Irc_Client_CmdQuit (const char *prefix, const char *params, const char *trailing)
00850 {
00851     char nick[MAX_VAR];
00852     irc_nick_prefix_t p;
00853     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00854     Irc_AppendToBuffer("^BQuits: %s (%s)", nick, trailing);
00855     Irc_Logic_RemoveChannelName(chan, nick);
00856 }
00857 
00858 static void Irc_Client_CmdKill (const char *prefix, const char *params, const char *trailing)
00859 {
00860     char nick[MAX_VAR];
00861     irc_nick_prefix_t p;
00862     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00863     Irc_AppendToBuffer("^BKilled: %s (%s)", nick, trailing);
00864     Irc_Logic_RemoveChannelName(chan, nick);
00865 }
00866 
00867 static void Irc_Client_CmdKick (const char *prefix, const char *params, const char *trailing)
00868 {
00869     char buf[IRC_SEND_BUF_SIZE];
00870     char nick[MAX_VAR];
00871     irc_nick_prefix_t p;
00872     const char *channel, *victim;
00873     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00874     strcpy(buf, params);
00875     channel = strtok(buf, " ");
00876     victim = strtok(NULL, " ");
00877     if (!strcmp(victim, irc_nick->string)) {
00878         /* we have been kicked */
00879         Irc_AppendToBuffer("^BYou were kicked from %s by %s (%s)", channel, nick, trailing);
00880     } else {
00881         /* someone else was kicked */
00882         Irc_AppendToBuffer("^B%s kicked %s (%s)", nick, victim, trailing);
00883     }
00884     Irc_Logic_RemoveChannelName(chan, nick);
00885 }
00886 
00890 static void Irc_Client_CmdNick (const char *prefix, const char *params, const char *trailing)
00891 {
00892     char nick[MAX_VAR];
00893     irc_nick_prefix_t p;
00894 
00895     /* not connected */
00896     if (!chan)
00897         return;
00898 
00899     Irc_ParseName(prefix, nick, sizeof(nick), &p);
00900     if (!strcmp(irc_nick->string, nick))
00901         Cvar_ForceSet("cl_name", trailing);
00902     Irc_AppendToBuffer("%s is now known as %s", nick, trailing);
00903     Irc_Logic_RemoveChannelName(chan, nick);
00904     Irc_Logic_AddChannelName(chan, p, trailing);
00905 }
00906 
00907 #define IRC_CTCP_MARKER_CHR '\001'
00908 #define IRC_CTCP_MARKER_STR "\001"
00909 
00913 static void Irc_Client_CmdPrivmsg (const char *prefix, const char *params, const char *trailing)
00914 {
00915     char nick[MAX_VAR];
00916     char * const emph = strchr(prefix, '!');
00917     char * ctcp = strchr(trailing, IRC_CTCP_MARKER_CHR);
00918     memset(nick, 0, sizeof(nick));
00919     if (emph)
00920         memcpy(nick, prefix, emph - prefix);
00921     else
00922         strcpy(nick, prefix);
00923 
00924     if (ctcp) {
00925         if (!strcmp(trailing + 1, "VERSION" IRC_CTCP_MARKER_STR)) {
00926             /* response with the game version */
00927             Irc_Proto_Msg(irc_defaultChannel->string, Cvar_GetString("version"));
00928             /*Irc_Proto_Notice(nick, IRC_CTCP_MARKER_STR "VERSION " UFO_VERSION " " CPUSTRING " " __DATE__ " " BUILDSTRING);*/
00929             Com_DPrintf(DEBUG_CLIENT, "Irc_Client_CmdPrivmsg: Response to version query\n");
00930         } else if (!strncmp(trailing + 1, "PING", 4)) {
00931             /* Used to measure the delay of the IRC network between clients. */
00932             char response[IRC_SEND_BUF_SIZE];
00933             strcpy(response, trailing);
00934             response[2] = 'O'; /* PING => PONG */
00935             Irc_Proto_Notice(nick, response);
00936         } else if (!strcmp(trailing + 1, "TIME" IRC_CTCP_MARKER_STR)) {
00937             const time_t t = time(NULL);
00938             char response[IRC_SEND_BUF_SIZE];
00939             const size_t response_len = sprintf(response, IRC_CTCP_MARKER_STR "TIME :%s" IRC_CTCP_MARKER_STR, ctime(&t));
00940             response[response_len - 1] = '\0';  /* remove trailing \n */
00941             Irc_Proto_Notice(nick, response);
00942         } else {
00943             Com_Printf("Irc_Client_CmdPrivmsg: Unknown ctcp command: '%s'\n", trailing);
00944         }
00945     } else {
00946         if (!strncmp(trailing, IRC_INVITE_FOR_A_GAME, strlen(IRC_INVITE_FOR_A_GAME))) {
00947             char serverIPAndPort[128];
00948             char *port;
00949             Q_strncpyz(serverIPAndPort, trailing + strlen(IRC_INVITE_FOR_A_GAME), sizeof(serverIPAndPort));
00950             /* values are splitted by ; */
00951             port = strstr(serverIPAndPort, ";");
00952             if (port == NULL) {
00953                 Com_DPrintf(DEBUG_CLIENT, "Invalid irc invite message received\n");
00954                 return;
00955             }
00956 
00957             /* split ip and port */
00958             *port++ = '\0';
00959 
00961             UI_ExecuteConfunc("multiplayer_invite_server_info %s %s", serverIPAndPort, port);
00962 
00963             UI_PushWindow("multiplayer_invite", NULL);
00964         } else if (!Irc_AppendToBuffer("<%s> %s", nick, trailing)) {
00965             /* check whether this is no message to the channel - but to the user */
00966             if (params && strcmp(params, irc_defaultChannel->string)) {
00967                 S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
00968                 MP_AddChatMessage(va("<%s> %s\n", nick, trailing));
00969             } else if (strstr(trailing, irc_nick->string)) {
00970                 S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
00971                 MP_AddChatMessage(va("<%s> %s\n", nick, trailing));
00972                 if (strcmp(UI_GetActiveWindowName(), "irc") && strcmp(UI_GetActiveWindowName(), mn_hud->string)) {
00973                     /* we are not in hud mode, nor in the lobby menu, use a popup */
00974                     UI_PushWindow("chat_popup", NULL);
00975                 }
00976             }
00977         }
00978 
00979         if (UI_GetActiveWindow() && strcmp(UI_GetActiveWindowName(), "irc")) {
00980             Com_Printf(COLORED_GREEN "<%s@lobby> %s\n", nick, trailing);
00981         }
00982     }
00983 }
00984 
00985 static void Irc_Client_CmdRplNamreply (const char *params, const char *trailing)
00986 {
00987     char *parseBuf, *pos;
00988     char *space;
00989     char nick[MAX_VAR];
00990     size_t len = strlen(trailing) + 1;
00991     irc_nick_prefix_t p;
00992 
00993     if (!chan)
00994         return;
00995 
00996     parseBuf = (char *)Mem_PoolAlloc(len * sizeof(char), cl_ircSysPool, 0);
00997     if (!parseBuf)
00998         return;
00999 
01000     Q_strncpyz(parseBuf, trailing, len);
01001     pos = parseBuf;
01002 
01003     do {
01004         /* names are space separated */
01005         space = strstr(pos, " ");
01006         if (space)
01007             *space = '\0';
01008         Irc_ParseName(pos, nick, sizeof(nick), &p);
01009         Irc_Logic_AddChannelName(chan, p, nick);
01010         if (space)
01011             pos = space + 1;
01012     } while (space && *pos);
01013 
01014     Mem_Free(parseBuf);
01015 }
01016 
01020 static void Irc_Client_CmdRplEndofnames (const char *params, const char *trailing)
01021 {
01022 }
01023 
01027 static qboolean Irc_Proto_ProcessServerMsg (const irc_server_msg_t *msg)
01028 {
01029     irc_command_t cmd;
01030     const char *p = NULL;
01031     cmd.type = msg->type;
01032 
01035     switch (cmd.type) {
01036     case IRC_COMMAND_NUMERIC:
01037         cmd.id.numeric = msg->id.numeric;
01038         switch (cmd.id.numeric) {
01039         case RPL_HELLO:
01040         case RPL_WELCOME:
01041         case RPL_YOURHOST:
01042         case RPL_CREATED:
01043         case RPL_MYINFO:
01044         case RPL_MOTDSTART:
01045         case RPL_MOTD:
01046         case RPL_LOCALUSERS:
01047         case RPL_GLOBALUSERS:
01048         case RPL_ISUPPORT:
01049         case RPL_LUSEROP:
01050         case RPL_LUSERUNKNOWN:
01051         case RPL_LUSERCHANNELS:
01052         case RPL_LUSERCLIENT:
01053         case RPL_LUSERME:
01054             return qtrue;
01055 
01056         /* read our own motd */
01057         case RPL_ENDOFMOTD:
01058             {
01059                 byte *fbuf;
01060                 int size;
01061                 size = FS_LoadFile("irc_motd.txt", &fbuf);
01062                 if (size) {
01063                     Irc_AppendToBuffer("%s", (char *)fbuf);
01064                     FS_FreeFile(fbuf);
01065                 }
01066             }
01067             return qtrue;
01068 
01069         case RPL_NAMREPLY:
01070             Irc_Client_CmdRplNamreply(msg->params, msg->trailing);
01071             return qtrue;
01072         case RPL_ENDOFNAMES:
01073             Irc_Client_CmdRplEndofnames(msg->params, msg->trailing);
01074             return qtrue;
01075         case RPL_TOPIC:
01076             Irc_Client_CmdRplTopic(msg->params, msg->trailing);
01077             return qtrue;
01078         case RPL_NOTOPIC:
01079             return qtrue;
01080         case RPL_WHOISUSER:
01081             Irc_Client_CmdRplWhoisuser(msg->params, msg->trailing);
01082             return qtrue;
01083         case RPL_WHOISSERVER:
01084             Irc_Client_CmdRplWhoisserver(msg->params, msg->trailing);
01085             return qtrue;
01086         case RPL_WHOISIDLE:
01087             Irc_Client_CmdRplWhoisidle(msg->params, msg->trailing);
01088             return qtrue;
01089         case RPL_WHOISACCOUNT:
01090             Irc_Client_CmdRplWhoisaccount(msg->params, msg->trailing);
01091             return qtrue;
01092         case RPL_WHOREPLY:
01093             Irc_Client_CmdRplWhoreply(msg->params, msg->trailing);
01094             return qtrue;
01095         case RPL_WHOWASUSER:
01096             Irc_Client_CmdRplWhowasuser(msg->params, msg->trailing);
01097             return qtrue;
01098         case RPL_ENDOFWHO:
01099         case RPL_WHOISCHANNELS:
01100         case RPL_WHOISOPERATOR:
01101         case RPL_ENDOFWHOIS:
01102         case RPL_ENDOFWHOWAS:
01103             p = strchr(msg->params, ' ');
01104             if (p) {
01105                 ++p;
01106                 Irc_AppendToBuffer("%s %s", p, msg->trailing);
01107             }
01108             return qtrue;
01109 
01110         case ERR_NICKNAMEINUSE:
01111         case ERR_NOSUCHNICK:
01112         case ERR_NONICKNAMEGIVEN:
01113         case ERR_ERRONEUSNICKNAME:
01114         case ERR_NICKCOLLISION:
01115             Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
01116             UI_PushWindow("irc_changename", NULL);
01117             break;
01118         /* error codes */
01119         case ERR_NOSUCHSERVER:
01120         case ERR_NOSUCHCHANNEL:
01121         case ERR_CANNOTSENDTOCHAN:
01122         case ERR_TOOMANYCHANNELS:
01123         case ERR_WASNOSUCHNICK:
01124         case ERR_TOOMANYTARGETS:
01125         case ERR_NOORIGIN:
01126         case ERR_NORECIPIENT:
01127         case ERR_NOTEXTTOSEND:
01128         case ERR_NOTOPLEVEL:
01129         case ERR_WILDTOPLEVEL:
01130         case ERR_UNKNOWNCOMMAND:
01131         case ERR_NOMOTD:
01132         case ERR_NOADMININFO:
01133         case ERR_FILEERROR:
01134         case ERR_BANNICKCHANGE:
01135         case ERR_NCHANGETOOFAST:
01136         case ERR_USERNOTINCHANNEL:
01137         case ERR_NOTONCHANNEL:
01138         case ERR_USERONCHANNEL:
01139         case ERR_NOLOGIN:
01140         case ERR_SUMMONDISABLED:
01141         case ERR_USERSDISABLED:
01142         case ERR_NOTREGISTERED:
01143         case ERR_NEEDMOREPARAMS:
01144         case ERR_ALREADYREGISTRED:
01145         case ERR_NOPERMFORHOST:
01146         case ERR_PASSWDMISMATCH:
01147         case ERR_YOUREBANNEDCREEP:
01148         case ERR_BADNAME:
01149         case ERR_KEYSET:
01150         case ERR_CHANNELISFULL:
01151         case ERR_UNKNOWNMODE:
01152         case ERR_INVITEONLYCHAN:
01153         case ERR_BANNEDFROMCHAN:
01154         case ERR_BADCHANNELKEY:
01155         case ERR_NOPRIVILEGES:
01156         case ERR_CHANOPRIVSNEEDED:
01157         case ERR_CANTKILLSERVER:
01158         case ERR_NOOPERHOST:
01159         case ERR_UMODEUNKNOWNFLAG:
01160         case ERR_USERSDONTMATCH:
01161         case ERR_GHOSTEDCLIENT:
01162         case ERR_LAST_ERR_MSG:
01163         case ERR_SILELISTFULL:
01164         case ERR_NOSUCHGLINE:
01165         case ERR_BADPING:
01166         case ERR_TOOMANYDCC:
01167         case ERR_LISTSYNTAX:
01168         case ERR_WHOSYNTAX:
01169         case ERR_WHOLIMEXCEED:
01170             Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
01171             return qtrue;
01172         default:
01173             Com_DPrintf(DEBUG_CLIENT, "<%s> [%s] %s : %s\n", msg->prefix, cmd.id.string, msg->params, msg->trailing);
01174             return qtrue;
01175         } /* switch */
01176         break;
01177     case IRC_COMMAND_STRING:
01178         cmd.id.string = msg->id.string;
01179 #ifdef DEBUG
01180         Com_DPrintf(DEBUG_CLIENT, "<%s> [%s] %s : %s\n", msg->prefix, cmd.id.string, msg->params, msg->trailing);
01181 #endif
01182         if (!strncmp(cmd.id.string, "NICK", 4))
01183             Irc_Client_CmdNick(msg->prefix, msg->params, msg->trailing);
01184         else if (!strncmp(cmd.id.string, "QUIT", 4))
01185             Irc_Client_CmdQuit(msg->prefix, msg->params, msg->trailing);
01186         else if (!strncmp(cmd.id.string, "NOTICE", 6)) {
01187             if (irc_logConsole->integer)
01188                 Com_Printf("%s\n", msg->trailing);
01189         } else if (!strncmp(cmd.id.string, "PRIVMSG", 7))
01190             Irc_Client_CmdPrivmsg(msg->prefix, msg->params, msg->trailing);
01191         else if (!strncmp(cmd.id.string, "MODE", 4))
01192             Irc_Client_CmdMode(msg->prefix, msg->params, msg->trailing);
01193         else if (!strncmp(cmd.id.string, "JOIN", 4))
01194             Irc_Client_CmdJoin(msg->prefix, msg->params, msg->trailing);
01195         else if (!strncmp(cmd.id.string, "PART", 4))
01196             Irc_Client_CmdPart(msg->prefix, msg->trailing);
01197         else if (!strncmp(cmd.id.string, "TOPIC", 5))
01198             Irc_Client_CmdTopic(msg->prefix, msg->trailing);
01199         else if (!strncmp(cmd.id.string, "KILL", 4))
01200             Irc_Client_CmdKill(msg->prefix, msg->params, msg->trailing);
01201         else if (!strncmp(cmd.id.string, "KICK", 4))
01202             Irc_Client_CmdKick(msg->prefix, msg->params, msg->trailing);
01203         else if (!strncmp(cmd.id.string, "PING", 4))
01204             Irc_Proto_Pong(irc_nick->string, msg->params, msg->trailing[0] ? msg->trailing : NULL);
01205         else if (!strncmp(cmd.id.string, "ERROR", 5)) {
01206             Irc_Logic_Disconnect(msg->trailing);
01207             Q_strncpyz(popupText, msg->trailing, sizeof(popupText));
01208             UI_Popup(_("Error"), popupText);
01209         } else
01210             Irc_AppendToBuffer("%s", msg->trailing);
01211         break;
01212     } /* switch */
01213     return qfalse;
01214 }
01215 
01216 static qboolean Irc_Proto_ParseServerMsg (const char *txt, size_t txt_len, irc_server_msg_t *msg)
01217 {
01218     const char *c = txt;
01219     const char *end = txt + txt_len;
01220     msg->prefix[0] = '\0';
01221     msg->params[0] = '\0';
01222     msg->trailing[0] = '\0';
01223     if (c < end && *c == ':') {
01224         /* parse prefix */
01225         char *prefix = msg->prefix;
01226         int i = 1;
01227         const size_t size = sizeof(msg->prefix);
01228         ++c;
01229         while (c < end && *c != '\r' && *c != ' ') {
01230             if (i++ >= size)
01231                 return qtrue;
01232             *prefix++ = *c++;
01233         }
01234         *prefix = '\0';
01235         ++c;
01236     }
01237     if (c < end && *c != '\r') {
01238         /* parse command */
01239         if (c < end && *c >= '0' && *c <= '9') {
01240             /* numeric command */
01241             char command[4];
01242             int i;
01243             for (i = 0; i < 3; ++i) {
01244                 if (c < end && *c >= '0' && *c <= '9')
01245                     command[i] = *c++;
01246                 else
01247                     return qtrue;
01248             }
01249             command[3] = '\0';
01250             msg->type = IRC_COMMAND_NUMERIC;
01251             msg->id.numeric = atoi(command);
01252         } else if (c < end && *c != '\r') {
01253             /* string command */
01254             int i = 1;
01255             const size_t size = sizeof(msg->id.string);
01256             char *command = msg->id.string;
01257             while (c < end && *c != '\r' && *c != ' ') {
01258                 if (i++ >= size)
01259                     return qtrue;
01260                 *command++ = *c++;
01261             }
01262             *command = '\0';
01263             msg->type = IRC_COMMAND_STRING;
01264         } else
01265             return qtrue;
01266         if (c < end && *c == ' ') {
01267             /* parse params and trailing */
01268             char *params = msg->params;
01269             int i = 1;
01270             const size_t size = sizeof(msg->params);
01271             ++c;
01272 
01273             /* parse params */
01274             while (c < end && *c != '\r' && *c != ':') {
01275                 /* parse single param */
01276                 while (c < end && *c != '\r' && *c != ' ') {
01277                     if (i++ >= size)
01278                         return qtrue;
01279                     *params++ = *c++;
01280                 }
01281                 /* more params */
01282                 if (c + 1 < end && *c == ' ' && *(c + 1) != ':') {
01283                     if (i++ >= size)
01284                         return qtrue;
01285                     *params++ = ' ';
01286                 }
01287                 if (*c == ' ')
01288                     ++c;
01289             }
01290             *params = '\0';
01291             /* parse trailing */
01292             if (c < end && *c == ':') {
01293                 char *trailing = msg->trailing;
01294                 int i = 1;
01295                 const size_t size = sizeof(msg->trailing);
01296                 ++c;
01297                 while (c < end && *c != '\r') {
01298                     if (i++ >= size)
01299                         return qtrue;
01300                     *trailing++ = *c++;
01301                 }
01302                 *trailing = '\0';
01303             }
01304         }
01305     }
01306     return qfalse;
01307 }
01308 
01313 static qboolean Irc_Proto_Enqueue (const char *msg, size_t msg_len)
01314 {
01315     irc_bucket_message_t *m;
01316     irc_bucket_message_t *n;
01317     const int messageBucketSize = irc_messageBucketSize->integer;
01318     const int characterBucketSize = irc_characterBucketSize->integer;
01319 
01320     if (!irc_connected) {
01321         Com_Printf("Irc_Proto_Enqueue: not connected\n");
01322         return qtrue;
01323     }
01324 
01325     /* create message node */
01326     m = (irc_bucket_message_t*) Mem_Alloc(sizeof(*m));
01327     n = irc_bucket.first_msg;
01328     if (irc_bucket.message_size + 1 <= messageBucketSize && irc_bucket.character_size + msg_len <= characterBucketSize) {
01330         m->msg = (char*) Mem_Alloc(msg_len);
01331         memcpy(m->msg, msg, msg_len);
01332         m->msg_len = msg_len;
01333         m->next = NULL;
01334         /* append message node */
01335         if (n) {
01336             while (n->next)
01337                 n = n->next;
01338             n->next = m;
01339         } else {
01340             irc_bucket.first_msg = m;
01341         }
01342         /* update bucket sizes */
01343         ++irc_bucket.message_size;
01344         irc_bucket.character_size += msg_len;
01345         return qfalse;
01346     } else {
01347         Com_Printf("Bucket(s) full. Could not enqueue message\n");
01348         return qtrue;
01349     }
01350 }
01351 
01356 static void Irc_Proto_RefillBucket (void)
01357 {
01358     /* calculate token refill */
01359     const double characterBucketSize = irc_characterBucketSize->value;
01360     const double characterBucketRate = irc_characterBucketRate->value;
01361     const int ms = CL_Milliseconds();
01362     const int ms_delta = ms - irc_bucket.last_refill;
01363     const double char_delta = (ms_delta * characterBucketRate) / 1000;
01364     const double char_new = irc_bucket.character_token + char_delta;
01365     /* refill token (but do not exceed maximum) */
01366     irc_bucket.character_token = min(char_new, characterBucketSize);
01367     /* set timestamp so next refill can calculate delta */
01368     irc_bucket.last_refill = ms;
01369 }
01370 
01376 static void Irc_Proto_DrainBucket (void)
01377 {
01378     const double characterBucketBurst = irc_characterBucketBurst->value;
01379     irc_bucket_message_t *msg;
01380 
01381     /* remove messages whose size exceed our burst size (we can not send them) */
01382     for (msg = irc_bucket.first_msg; msg && msg->msg_len > characterBucketBurst; msg = irc_bucket.first_msg) {
01383         irc_bucket_message_t * const next = msg->next;
01384         /* update bucket sizes */
01385         --irc_bucket.message_size;
01386         irc_bucket.character_size -= msg->msg_len;
01387         /* free message */
01388         Mem_Free(msg->msg);
01389         /* dequeue message */
01390         irc_bucket.first_msg = next;
01391     }
01392     /* send burst of remaining messages */
01393     for (msg = irc_bucket.first_msg; msg; msg = irc_bucket.first_msg) {
01394         /* send message */
01395         Irc_Net_Send(msg->msg, msg->msg_len);
01396         irc_bucket.character_token -= msg->msg_len;
01397         /* dequeue message */
01398         irc_bucket.first_msg = msg->next;
01399         /* update bucket sizes */
01400         --irc_bucket.message_size;
01401         irc_bucket.character_size -= msg->msg_len;
01402         /* free message */
01403         Mem_Free(msg->msg);
01404         Mem_Free(msg);
01405     }
01406 }
01407 
01408 /*
01409 ===============================================================
01410 Logic functions
01411 ===============================================================
01412 */
01413 
01417 static void Irc_Logic_SendMessages (void)
01418 {
01419     Irc_Proto_RefillBucket();       /* first refill token */
01420     Irc_Proto_DrainBucket();    /* then send messages (if allowed) */
01421 }
01422 
01429 static void Irc_Logic_ReadMessages (void)
01430 {
01431     qboolean msg_complete;
01432     do {
01433         irc_server_msg_t msg;
01434         if (!Irc_Proto_PollServerMsg(&msg, &msg_complete)) {
01435             /* success */
01436             if (msg_complete)
01437                 Irc_Proto_ProcessServerMsg(&msg);
01438         } else {
01439             /* failure */
01440             UI_Popup(_("Error"), _("Server closed connection"));
01441             Irc_Logic_Disconnect("Server closed connection");
01442         }
01443     } while (msg_complete);
01444 }
01445 
01446 static void Irc_Logic_Connect (const char *server, const char *port)
01447 {
01448     if (!Irc_Proto_Connect(server, port)) {
01449         /* connected to server, send NICK and USER commands */
01450         const char * const pass = irc_password->string;
01451         const char * const user = irc_user->string;
01452         irc_connected = qtrue;
01453         if (pass[0] != '\0')
01454             Irc_Proto_Password(pass);
01455         Irc_Proto_Nick(irc_nick->string);
01456         Irc_Proto_User(user, qtrue, user);
01457     } else {
01458         Com_Printf("Could not connect to the irc server %s:%s\n", server, port);
01459     }
01460 }
01461 
01462 static void Irc_Logic_Disconnect (const char *reason)
01463 {
01464     if (irc_connected) {
01465         Com_Printf("Irc_Disconnect: %s\n", reason);
01466         Irc_Proto_Quit(reason);
01467         Irc_Proto_Disconnect();
01468         irc_connected = qfalse;
01469         chan = NULL;
01470         Cvar_ForceSet("irc_defaultChannel", "");
01471         Cvar_ForceSet("irc_topic", "Connecting (please wait)...");
01472         UI_ResetData(TEXT_IRCUSERS);
01473         Irc_Input_Deactivate_f();
01474     } else
01475         Com_Printf("Irc_Disconnect: not connected\n");
01476 }
01477 
01482 void Irc_Logic_Frame (void)
01483 {
01484     if (irc_connected) {
01485         if (irc_channel->modified) {
01487             Irc_Logic_Disconnect("Switched to another channel");
01488             Irc_Logic_Connect(irc_server->string, irc_port->string);
01489             if (irc_connected)
01490                 Irc_Client_Join(irc_channel->string, NULL);
01491         }
01492         /* could have changed in the meantime */
01493         if (irc_connected) {
01494             Irc_Logic_SendMessages();
01495             Irc_Logic_ReadMessages();
01496         }
01497     }
01498     irc_channel->modified = qfalse;
01499 }
01500 
01501 static const char *Irc_Logic_GetChannelTopic (const irc_channel_t *channel)
01502 {
01503     assert(channel);
01504     return channel->topic;
01505 }
01506 
01511 static void Irc_Logic_AddChannelName (irc_channel_t *channel, irc_nick_prefix_t prefix, const char *nick)
01512 {
01513     int i;
01514     /* first one */
01515     irc_user_t* user = channel->user;
01516     for (i = 0; user && i < channel->users; i++, user = user->next) {
01517         if (!strncmp(&(user->key[1]), nick, MAX_VAR - 1))
01518             return;
01519     }
01520     user = Mem_PoolAlloc(sizeof(*user), cl_ircSysPool, 0);
01521     user->next = channel->user;
01522     channel->user = user;
01523 
01524     Com_sprintf(user->key, sizeof(user->key), "%c%s", prefix, nick);
01525     channel->users++;
01526     Com_DPrintf(DEBUG_CLIENT, "Add user '%s' to userlist at pos %i\n", user->key, channel->users);
01527     Irc_Client_Names_f();
01528 }
01529 
01534 static void Irc_Logic_RemoveChannelName (irc_channel_t *channel, const char *nick)
01535 {
01536     int i;
01537     /* first one */
01538     irc_user_t* user = channel->user;
01539     irc_user_t* predecessor = NULL;
01540     for (i = 0; user && i < channel->users; i++, user = user->next) {
01541         if (!strncmp(&(user->key[1]), nick, sizeof(user->key))) {
01542             /* delete the first user from the list */
01543             if (!predecessor)
01544                 channel->user = user->next;
01545             /* point to the descendant */
01546             else
01547                 predecessor->next = user->next;
01548             Mem_Free(user);
01549             chan->users--;
01550             Irc_Client_Names_f();
01551             return;
01552         }
01553         predecessor = user;
01554     }
01555 }
01556 
01557 /*
01558 ===============================================================
01559 Network functions
01560 ===============================================================
01561 */
01562 
01567 static qboolean Irc_Net_Connect (const char *host, const char *port)
01568 {
01569     if (irc_stream)
01570         NET_StreamFree(irc_stream);
01571     irc_stream = NET_Connect(host, port);
01572     return irc_stream ? qfalse : qtrue;
01573 }
01574 
01578 static qboolean Irc_Net_Disconnect (void)
01579 {
01580     NET_StreamFree(irc_stream);
01581     irc_stream = NULL;
01582     return qtrue;
01583 }
01584 
01585 static void Irc_Net_Send (const char *msg, size_t msg_len)
01586 {
01587     assert(msg);
01588     NET_StreamEnqueue(irc_stream, msg, msg_len);
01589 }
01590 
01591 /*
01592 ===============================================================
01593 Bindings
01594 ===============================================================
01595 */
01596 
01597 static void Irc_Connect_f (void)
01598 {
01599     const int argc = Cmd_Argc();
01600     if (!irc_port || !irc_server)
01601         return;
01602     if (argc >= 2 && argc <= 4) {
01603 #if 0
01604         if (irc_connected)
01605             Irc_Logic_Disconnect("reconnect");
01606 #endif
01607         if (!irc_connected) {
01608             /* not connected yet */
01609             if (argc >= 2)
01610                 Cvar_Set("irc_server", Cmd_Argv(1));
01611             if (argc >= 3)
01612                 Cvar_Set("irc_port", Cmd_Argv(2));
01613             Com_Printf("Connecting to %s:%s\n", irc_server->string, irc_port->string);
01614             Irc_Logic_Connect(irc_server->string, irc_port->string);
01615             if (irc_connected && argc >= 4)
01616                 Irc_Client_Join(Cmd_Argv(3), NULL);
01617         } else
01618             Com_Printf("Already connected.\n");
01619 
01620     } else if (irc_server->string[0] != '\0' && irc_port->integer) {
01621         if (!irc_connected)
01622             Cbuf_AddText(va("irc_connect %s %i %s\n", irc_server->string, irc_port->integer, irc_channel->string));
01623         else
01624             Com_Printf("Already connected.\n");
01625     } else
01626         Com_Printf("Usage: %s [<server>] [<port>] [<channel>]\n", Cmd_Argv(0));
01627 }
01628 
01629 static void Irc_Disconnect_f (void)
01630 {
01631     Irc_Logic_Disconnect("normal exit");
01632 }
01633 
01634 static qboolean Irc_Client_Join (const char *channel, const char *password)
01635 {
01636     if (!Irc_IsChannel(channel)) {
01637         Com_Printf("No valid channel name\n");
01638         return qfalse;
01639     }
01640     /* join desired channel */
01641     if (!Irc_Proto_Join(channel, password)) {
01642         Cvar_ForceSet("irc_defaultChannel", channel);
01643         Com_Printf("Joined channel: '%s'\n", channel);
01644         return qtrue;
01645     } else {
01646         Com_Printf("Could not join channel: '%s'\n", channel);
01647         return qfalse;
01648     }
01649 }
01650 
01651 static void Irc_Client_Join_f (void)
01652 {
01653     const int argc = Cmd_Argc();
01654     if (argc >= 2 && argc <= 3) {
01655         const char * const channel = Cmd_Argv(1);
01656         /* password is optional */
01657         const char * const channel_pass = (argc == 3) ? Cmd_Argv(2) : NULL;
01658         Irc_Client_Join(channel, channel_pass);
01659     } else
01660         Com_Printf("Usage: %s <channel> [<password>]\n", Cmd_Argv(0));
01661 }
01662 
01663 static void Irc_Client_Part_f (void)
01664 {
01665     const int argc = Cmd_Argc();
01666     if (argc == 2) {
01667         const char * const channel = Cmd_Argv(1);
01668         Irc_Proto_Part(channel);
01669     } else
01670         Com_Printf("Usage: %s <channel>\n", Cmd_Argv(0));
01671 }
01672 
01678 static void Irc_Client_Msg_f (void)
01679 {
01680     char cropped_msg[IRC_SEND_BUF_SIZE];
01681     const char *msg;
01682 
01683     if (Cmd_Argc() >= 2)
01684         msg = Cmd_Args();
01685     else if (irc_send_buffer->string[0] != '\0')
01686         msg = irc_send_buffer->string;
01687     else
01688         return;
01689 
01690     if (msg[0] != '\0') {
01691         if (irc_defaultChannel->string[0] != '\0') {
01692             if (msg[0] == '"') {
01693                 const size_t msg_len = strlen(msg);
01694                 memcpy(cropped_msg, msg + 1, msg_len - 2);
01695                 cropped_msg[msg_len - 2] = '\0';
01696                 msg = cropped_msg;
01697             }
01698             if (!Irc_Proto_Msg(irc_defaultChannel->string, msg)) {
01699                 /* local echo */
01700                 Irc_AppendToBuffer("<%s> %s", irc_nick->string, msg);
01701             }
01702             Cvar_ForceSet("irc_send_buffer", "");
01703         } else
01704             Com_Printf("Join a channel first.\n");
01705     }
01706 }
01707 
01708 static void Irc_Client_PrivMsg_f (void)
01709 {
01710     if (Cmd_Argc() >= 3) {
01711         char cropped_msg[IRC_SEND_BUF_SIZE];
01712         const char * const target = Cmd_Argv(1);
01713         const char *msg = Cmd_Args() + strlen(target) + 1;
01714         if (*msg == '"') {
01715             size_t msg_len = strlen(msg);
01716             memcpy(cropped_msg, msg + 1, msg_len - 2);
01717             cropped_msg[msg_len - 2] = '\0';
01718             msg = cropped_msg;
01719         }
01720         Irc_Proto_Msg(target, msg);
01721     } else
01722         Com_Printf("Usage: %s <target> {<msg>}\n", Cmd_Argv(0));
01723 }
01724 
01725 static void Irc_Client_Mode_f (void)
01726 {
01727     const int argc = Cmd_Argc();
01728     if (argc >= 3) {
01729         const char * const target = Cmd_Argv(1);
01730         const char * const modes = Cmd_Argv(2);
01731         const char * const params = argc >= 4
01732             ? Cmd_Args() + strlen(target) + strlen(modes) + 2
01733             : NULL;
01734         Irc_Proto_Mode(target, modes, params);
01735     } else
01736         Com_Printf("Usage: %s <target> <modes> {<param>}\n", Cmd_Argv(0));
01737 }
01738 
01739 static void Irc_Client_Topic_f (void)
01740 {
01741     const int argc = Cmd_Argc();
01742     if (argc >= 2) {
01743         const char * const channel = Cmd_Argv(1);
01744         if (chan) {
01745             if (argc >= 3) {
01746                 char buf[1024];
01747                 const char *in = Cmd_Args();
01748                 char *out = buf;
01749                 if (*in == '"')
01750                     in += 2;
01751                 in += strlen(channel) + 1;
01752                 Q_strncpyz(out, in, sizeof(out));
01753                 if (*out == '"') {
01754                     size_t out_len;
01755                     ++out;
01756                     out_len = strlen(out);
01757                     assert(out_len >= 1);
01758                     out[out_len - 1] = '\0';
01759                 }
01760                 Irc_Proto_Topic(channel, out);
01761             } else
01762                 Com_Printf("%s topic: \"%s\"\n", channel, Irc_Logic_GetChannelTopic(chan));
01763         } else
01764             Com_Printf("Not joined: %s\n", channel);
01765     } else
01766         Com_Printf("Usage: %s <channel> [<topic>]\n", Cmd_Argv(0));
01767 }
01768 
01769 #define IRC_MAX_USERLIST 512
01770 static char irc_userListOrdered[IRC_MAX_USERLIST][MAX_VAR];
01771 
01772 static void Irc_Client_Names_f (void)
01773 {
01774     irc_user_t* user;
01775     if (chan) {
01776         linkedList_t *irc_names_buffer = NULL;
01777         int i;
01778         user = chan->user;
01779         for (i = 0; i < chan->users; i++) {
01780             if (i >= IRC_MAX_USERLIST)
01781                 break;
01782             Q_strncpyz(irc_userListOrdered[i], user->key, MAX_VAR);
01783             user = user->next;
01784         }
01785         if (i > 0) {
01786             qsort((void *)irc_userListOrdered, i, MAX_VAR, Q_StringSort);
01787             while (i--)
01788                 LIST_AddString(&irc_names_buffer, irc_userListOrdered[i]);
01789         }
01790         UI_RegisterLinkedListText(TEXT_IRCUSERS, irc_names_buffer);
01791     } else
01792         Com_Printf("Not joined\n");
01793 }
01794 
01795 static void Irc_Client_Kick_f (void)
01796 {
01797     const int argc = Cmd_Argc();
01798     if (argc >= 3) {
01799         const char *channel = Cmd_Argv(1);
01800         if (chan) {
01801             const char * const nick = Cmd_Argv(2);
01802             const char *reason;
01803             if (argc >= 4)
01804                 reason = Cmd_Args() + strlen(nick) + strlen(channel) + 2;
01805             else
01806                 reason = NULL;
01807             Irc_Proto_Kick(channel, nick, reason);
01808         } else
01809             Com_Printf("Not joined: %s.\n", channel);
01810     } else
01811         Com_Printf("Usage: %s <channel> <nick> [<reason>]\n", Cmd_Argv(0));
01812 }
01813 
01817 static void Irc_Client_Invite_f (void)
01818 {
01819     const irc_user_t *user;
01820     char buf[128];
01821     const char *node;
01822 
01823     if (!CL_OnBattlescape()) {
01824         Com_Printf("You must be connected to a running server to invite others\n");
01825         return;
01826     }
01827 
01828     if (!chan) {
01829         UI_PushWindow("irc_popup", NULL);
01830         return;
01831     }
01832 
01834     node = "127.0.0.1";
01835     Com_sprintf(buf, sizeof(buf), "%s%s;%s", IRC_INVITE_FOR_A_GAME, node, port->string);
01836     user = chan->user;
01837     while (user) {
01840         /* the first character is a prefix, skip it when checking
01841          * against our own nickname */
01842         const char *name = &(user->key[1]);
01843         if (strcmp(name, irc_nick->string))
01844             Irc_Proto_Msg(name, buf);
01845         user = user->next;
01846     }
01847 }
01848 
01849 static void Irc_Client_Who_f (void)
01850 {
01851     if (Cmd_Argc() == 2) {
01852         Irc_Proto_Who(Cmd_Argv(1));
01853     } else
01854         Com_Printf("Usage: %s <usermask>\n", Cmd_Argv(0));
01855 }
01856 
01857 static void Irc_Client_Whois_f (void)
01858 {
01859     if (Cmd_Argc() == 2) {
01860         Irc_Proto_Whois(Cmd_Argv(1));
01861     } else
01862         Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
01863 }
01864 
01865 static void Irc_Client_Whowas_f (void)
01866 {
01867     if (Cmd_Argc() == 2) {
01868         Irc_Proto_Whowas(Cmd_Argv(1));
01869     } else
01870         Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
01871 }
01872 
01873 /*
01874 ===============================================================
01875 Menu functions
01876 ===============================================================
01877 */
01878 
01883 static void Irc_UserClick_f (void)
01884 {
01885     const char *name;
01886     int num, cnt;
01887 
01888     if (Cmd_Argc() != 2)
01889         return;
01890 
01891     if (!chan)
01892         return;
01893 
01894     num = atoi(Cmd_Argv(1));
01895     if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
01896         return;
01897 
01898     cnt = min(chan->users, IRC_MAX_USERLIST);
01899     cnt -= num + 1;
01900 
01901     name = irc_userListOrdered[cnt];
01902     Cvar_Set("irc_send_buffer", va("%s%s: ", irc_send_buffer->string, &name[1]));
01903 }
01904 
01909 static void Irc_UserRightClick_f (void)
01910 {
01911     const char *name;
01912     int num, cnt;
01913 
01914     if (Cmd_Argc() != 2)
01915         return;
01916 
01917     if (!chan)
01918         return;
01919 
01920     num = atoi(Cmd_Argv(1));
01921     if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
01922         return;
01923 
01924     cnt = min(chan->users, IRC_MAX_USERLIST);
01925     cnt -= num + 1;
01926 
01927     name = irc_userListOrdered[cnt];
01928     Irc_Proto_Whois(&name[1]);
01929 }
01930 
01934 static void Irc_Input_Activate_f (void)
01935 {
01936     /* in case of a failure we need this in UI_PopWindow */
01937     if (irc_connected && irc_defaultChannel->string[0] != '\0') {
01938         UI_RegisterText(TEXT_IRCCONTENT, irc_buffer);
01939     } else {
01940         Com_DPrintf(DEBUG_CLIENT, "Irc_Input_Activate: Warning - IRC not connected\n");
01941         UI_PopWindow(qfalse);
01942         UI_PushWindow("irc_popup", NULL);
01943         /* cancel any active editing */
01944         return;
01945     }
01946 }
01947 
01951 static void Irc_Input_Deactivate_f (void)
01952 {
01953     irc_send_buffer->modified = qfalse;
01954 
01955     UI_ResetData(TEXT_IRCCONTENT);
01956 }
01957 
01958 /*
01959 ===============================================================
01960 Init and Shutdown functions
01961 ===============================================================
01962 */
01963 
01964 void Irc_Init (void)
01965 {
01966     /* commands */
01967     Cmd_AddCommand("irc_join", Irc_Client_Join_f, "Join an irc channel");
01968     Cmd_AddCommand("irc_connect", Irc_Connect_f, "Connect to the irc network");
01969     Cmd_AddCommand("irc_disconnect", Irc_Disconnect_f, "Disconnect from the irc network");
01970     Cmd_AddCommand("irc_part", Irc_Client_Part_f, NULL);
01971     Cmd_AddCommand("irc_privmsg", Irc_Client_PrivMsg_f, NULL);
01972     Cmd_AddCommand("irc_mode", Irc_Client_Mode_f, NULL);
01973     Cmd_AddCommand("irc_who", Irc_Client_Who_f, NULL);
01974     Cmd_AddCommand("irc_whois", Irc_Client_Whois_f, NULL);
01975     Cmd_AddCommand("irc_whowas", Irc_Client_Whowas_f, NULL);
01976     Cmd_AddCommand("irc_chanmsg", Irc_Client_Msg_f, NULL);
01977     Cmd_AddCommand("irc_topic", Irc_Client_Topic_f, NULL);
01978     Cmd_AddCommand("irc_names", Irc_Client_Names_f, NULL);
01979     Cmd_AddCommand("irc_kick", Irc_Client_Kick_f, NULL);
01980     Cmd_AddCommand("irc_invite", Irc_Client_Invite_f, "Invite other players for a game");
01981 
01982     Cmd_AddCommand("irc_userlist_click", Irc_UserClick_f, "Menu function for clicking a user from the list");
01983     Cmd_AddCommand("irc_userlist_rclick", Irc_UserRightClick_f, "Menu function for clicking a user from the list");
01984 
01985     Cmd_AddCommand("irc_activate", Irc_Input_Activate_f, "IRC init when entering the menu");
01986     Cmd_AddCommand("irc_deactivate", Irc_Input_Deactivate_f, "IRC deactivate when leaving the irc menu");
01987 
01988     /* cvars */
01989     irc_server = Cvar_Get("irc_server", "irc.freenode.org", CVAR_ARCHIVE, "IRC server to connect to");
01990     irc_channel = Cvar_Get("irc_channel", "#ufoai-gamer", CVAR_ARCHIVE, "IRC channel to join into");
01991     irc_channel->modified = qfalse;
01992     irc_port = Cvar_Get("irc_port", "6667", CVAR_ARCHIVE, "IRC port to connect to");
01993     irc_user = Cvar_Get("irc_user", "UFOAIPlayer", CVAR_ARCHIVE, NULL);
01994     irc_password = Cvar_Get("irc_password", "", CVAR_ARCHIVE, NULL);
01995     irc_topic = Cvar_Get("irc_topic", "Connecting (please wait)...", CVAR_NOSET, NULL);
01996     irc_defaultChannel = Cvar_Get("irc_defaultChannel", "", CVAR_NOSET, NULL);
01997     irc_logConsole = Cvar_Get("irc_logConsole", "0", CVAR_ARCHIVE, "Log all irc conversations to game console, too");
01998     irc_showIfNotInMenu = Cvar_Get("irc_showIfNotInMenu", "0", CVAR_ARCHIVE, "Show chat messages on top of the menu stack if we are not in the irc menu");
01999     irc_send_buffer = Cvar_Get("irc_send_buffer", "", 0, NULL);
02000     irc_nick = Cvar_Get("cl_name", "", 0, NULL);
02001 
02002     irc_messageBucketSize = Cvar_Get("irc_messageBucketSize", "100", CVAR_ARCHIVE, "max 100 messages in bucket");
02003     irc_messageBucketBurst = Cvar_Get("irc_messageBucketBurst", "5", CVAR_ARCHIVE, "max burst size is 5 messages");
02004     irc_characterBucketSize = Cvar_Get("irc_characterBucketSize", "2500", CVAR_ARCHIVE, "max 2,500 characters in bucket");
02005     irc_characterBucketBurst = Cvar_Get("irc_characterBucketBurst", "250", CVAR_ARCHIVE, "max burst size is 250 characters");
02006     irc_characterBucketRate = Cvar_Get("irc_characterBucketRate", "10", CVAR_ARCHIVE, "per second (100 characters in 10 seconds)");
02007 }
02008 
02009 void Irc_Shutdown (void)
02010 {
02011     if (irc_connected)
02012         Irc_Logic_Disconnect("shutdown");
02013 }

Generated by  doxygen 1.6.2