fteqw/plugins/irc/ircclient.c

2790 lines
73 KiB
C
Raw Normal View History

//Released under the terms of the gpl as this file uses a bit of quake derived code. All sections of the like are marked as such
// changes name to while in channel
// mode command
// Spike can you implement nick tab completion. ~moodles
// need option for whois on receiving PM
// bug: setting channel to private, crashes fte when trying to join it.
// http://www.mirc.net/raws/
// http://www.ircle.com/reference/commands.shtml
#include "../plugin.h"
#include <time.h>
#include <ctype.h>
#include "../../engine/common/netinc.h"
#define handleisvalid(h) ((h)>=0)
#define invalid_handle -1
enum tlsmode_e
{
TLS_OFF,
TLS_INITIAL, //tls only
TLS_START, //tls upgrade
TLS_STARTING //don't send any nick/user/pass info while this is set
};
#define irccvars "IRC Console Variables"
static vmcvar_t irc_debug = {"irc_debug", "0", irccvars, 0};
static vmcvar_t irc_motd = {"irc_motd", "0", irccvars, 0};
static vmcvar_t irc_nick = {"irc_nick", "", irccvars, 0};
static vmcvar_t irc_altnick = {"irc_altnick", "", irccvars, 0};
static vmcvar_t irc_realname = {"irc_realname", "FTE IRC-Plugin", irccvars, 0};
static vmcvar_t irc_hostname = {"irc_hostname", "localhost", irccvars, 0};
static vmcvar_t irc_username = {"irc_username", "FTE", irccvars, 0};
static vmcvar_t irc_timestamp = {"irc_timestamp", "0", irccvars, 0};
static vmcvar_t irc_quitmessage = {"irc_quitmessage", "", irccvars, 0};
static vmcvar_t irc_config = {"irc_config", "1", irccvars, 0};
#undef irccvars
static vmcvar_t *cvarlist[] ={
&irc_debug,
&irc_motd,
&irc_nick,
&irc_altnick,
&irc_realname,
&irc_hostname,
&irc_username,
&irc_timestamp,
&irc_quitmessage,
NULL
};
static icefuncs_t *piceapi;
static int next_window_x;
static int next_window_y;
static qboolean reloadconfig;
//static char commandname[64]; // belongs to magic tokenizer
static char subvar[9][1000]; // etghack
static char casevar[9][1000]; //numbered_command
//static char servername[64]; // store server name
#define CURRENTCONSOLE "" // need to make this the current console
#define DEFAULTCONSOLE ""
#define COMMANDNAME "irc"
#define RELEASE __DATE__
static void (*Con_TrySubPrint)(const char *subname, const char *text);
static void Con_FakeSubPrint(const char *subname, const char *text)
{
pCon_Print(text);
}
//porting zone:
#define COLOURGREEN "^2"
#define COLORWHITE "^7"
#define COLOURWHITE "^7" // word
#define COLOURRED "^1"
#define COLOURYELLOW "^3"
#define COLOURPURPLE "^5"
#define COLOURBLUE "^4"
#define COLOURINDIGO "^6"
#define IRC_Malloc malloc
#define IRC_Free free
#undef COM_Parse
static char *COM_Parse (char *data, char *token_out, int token_maxlen) //this is taken out of quake
{
int c;
int len;
len = 0;
token_out[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c=='/')
{
if (data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
}
// handle quoted strings specially
if (c == '\"')
{
data++;
while (1)
{
if (len >= token_maxlen-1)
return data;
c = *data++;
if (c=='\"' || !c)
{
token_out[len] = 0;
return data;
}
token_out[len] = c;
len++;
}
}
// parse a regular word
do
{
if (len >= token_maxlen-1)
return data;
token_out[len] = c;
data++;
len++;
c = *data;
} while (c>32);
token_out[len] = 0;
return data;
}
//\r\n is used to end a line.
//meaning \0s are valid.
//but never used cos it breaks strings
#define IRC_MAXNICKLEN 32 //9 and a null term
#define IRC_MAXMSGLEN 512
#define IRC_MAXOUTBUFFER (IRC_MAXMSGLEN*16)
typedef struct ircclient_s {
struct ircclient_s *next;
char id[16]; //used for console prints, so we can match up consoles and clients.
char server[64];
int port;
qhandle_t socket;
enum tlsmode_e tlsmode;
qboolean quitting;
qboolean connecting;
char nick[IRC_MAXNICKLEN]; //nick that we're actually using
size_t nicktries; //so we can cycle nicks till we get one that works.
qboolean persist; //server connection is persistent across restarts
char primarynick[IRC_MAXNICKLEN]; //primary nick the connection was configured with
char pwd[128]; //server password
char realname[128]; //this is your descriptive OS user account... supposedly.
char username[128]; //this is your unique OS user name... supposedly.
char hostname[128]; //this is your OS hostname... supposedly.
char autochannels[256]; //"#chan,pwd #foo,bar #fred #splodge" for four channnels, two with a password
char defaultdest[IRC_MAXNICKLEN];//channel or nick
char bufferedinmessage[IRC_MAXMSGLEN+1]; //there is a max size for protocol. (conveinient eh?) (and it's text format)
int bufferedinammount;
char bufferedoutmessage[IRC_MAXOUTBUFFER+1]; //there is a max size for protocol. (conveinient eh?) (and it's text format)
int bufferedoutammount;
struct ircice_s
{
struct ircice_s *next;
enum iceproto_e type;
char peer[IRC_MAXNICKLEN];
qboolean host; //the host is the person that initiated the call/etc. they're the ones responsible for resolving deadlocks.
qboolean allowed; //user allowed it. woot.
qboolean accepted; //peer accepted it. connection is active.
struct icestate_s *ice;
} *ice;
} ircclient_t;
static ircclient_t *ircclients;
static void IRC_SetFooter(ircclient_t *irc, const char *subname, const char *format, ...)
{
va_list argptr;
static char string[1024];
char lwr[128];
int i;
const char *channame = subname;
va_start (argptr, format);
Q_vsnprintf (string, sizeof(string), format,argptr);
va_end (argptr);
if (irc)
{
Q_strlcpy(lwr, irc->id, sizeof(lwr));
for (i = strlen(lwr); *subname && i < sizeof(lwr)-2; i++, subname++)
{
if (*subname >= 'A' && *subname <= 'Z')
lwr[i] = *subname - 'A' + 'a';
else
lwr[i] = *subname;
}
lwr[i] = '\0';
if (BUILTINISVALID(Con_SetConsoleFloat) && pCon_GetConsoleFloat(lwr, "iswindow") < true)
{
pCon_SetConsoleString(lwr, "title", *channame?channame:irc->server);
pCon_SetConsoleString(lwr, "prompt", va("[^1%s^7]: ", irc->nick));
pCon_SetConsoleFloat(lwr, "iswindow", 2);
pCon_SetConsoleFloat(lwr, "forceutf8", true);
pCon_SetConsoleFloat(lwr, "wnd_w", 256);
pCon_SetConsoleFloat(lwr, "wnd_h", 320);
//lame, but whatever.
if (next_window_x + 256 > pvid.width)
{
next_window_x = 0;
next_window_y += 320;
if (next_window_y + 320 > pvid.height)
next_window_y = 0;
}
pCon_SetConsoleFloat(lwr, "wnd_x", next_window_x);
pCon_SetConsoleFloat(lwr, "wnd_y", next_window_y);
next_window_x += 256;
}
if (BUILTINISVALID(Con_SetConsoleString))
pCon_SetConsoleString(lwr, "footer", string);
}
}
static qboolean IRC_WindowShown(ircclient_t *irc, const char *subname)
{
char lwr[128];
int i;
if (irc)
{
Q_strlcpy(lwr, irc->id, sizeof(lwr));
for (i = strlen(lwr); *subname && i < sizeof(lwr)-2; i++, subname++)
{
if (*subname >= 'A' && *subname <= 'Z')
lwr[i] = *subname - 'A' + 'a';
else
lwr[i] = *subname;
}
lwr[i] = '\0';
if (BUILTINISVALID(Con_SetConsoleFloat) && pCon_GetConsoleFloat(lwr, "iswindow") < true)
return false;
}
return true;
}
static void IRC_Printf(ircclient_t *irc, const char *subname, const char *format, ...) LIKEPRINTF(3);
static void IRC_Printf(ircclient_t *irc, const char *subname, const char *format, ...)
{
va_list argptr;
static char string[1024];
char lwr[128];
int i;
const char *channame = subname;
va_start (argptr, format);
Q_vsnprintf (string, sizeof(string), format,argptr);
va_end (argptr);
if (!irc)
pCon_Print(string);
else
{
Q_strlcpy(lwr, irc->id, sizeof(lwr));
for (i = strlen(lwr); *subname && i < sizeof(lwr)-2; i++, subname++)
{
if (*subname >= 'A' && *subname <= 'Z')
lwr[i] = *subname - 'A' + 'a';
else
lwr[i] = *subname;
}
lwr[i] = '\0';
if (BUILTINISVALID(Con_SetConsoleFloat) && pCon_GetConsoleFloat(lwr, "iswindow") < true)
{
pCon_SetConsoleString(lwr, "title", *channame?channame:irc->server);
pCon_SetConsoleString(lwr, "prompt", va("[^1%s^7]: ", irc->nick));
pCon_SetConsoleFloat(lwr, "iswindow", 2);
pCon_SetConsoleFloat(lwr, "forceutf8", true);
pCon_SetConsoleFloat(lwr, "wnd_w", 256);
pCon_SetConsoleFloat(lwr, "wnd_h", 320);
//lame, but whatever.
if (next_window_x + 256 > pvid.width)
{
next_window_x = 0;
next_window_y += 320;
if (next_window_y + 320 > pvid.height)
next_window_y = 0;
}
pCon_SetConsoleFloat(lwr, "wnd_x", next_window_x);
pCon_SetConsoleFloat(lwr, "wnd_y", next_window_y);
next_window_x += 256;
}
if (!*string)
pCon_SetActive(lwr);
Con_TrySubPrint(lwr, string);
}
}
static void IRC_InitCvars(void)
{
vmcvar_t *v;
int i;
for (i=0; cvarlist[i]; i++)
{
v = cvarlist[i];
v->handle = pCvar_Register(v->name, v->string, v->flags, v->group);
}
}
static int IRC_CvarUpdate(void) // perhaps void instead?
{
vmcvar_t *v;
int i;
for (i=0; cvarlist[i]; i++)
{
v = cvarlist[i];
v->modificationcount = pCvar_Update(v->handle, &v->modificationcount, v->string, &v->value);
}
return 0;
}
void IRC_Command(ircclient_t *ircclient, char *dest, char *args);
qintptr_t IRC_ExecuteCommand(qintptr_t *args);
qintptr_t IRC_ConExecuteCommand(qintptr_t *args);
qintptr_t IRC_Frame(qintptr_t *args);
qintptr_t IRC_ConsoleLink(qintptr_t *args);
qintptr_t Plug_Init(qintptr_t *args)
{
if ( Plug_Export("Tick", IRC_Frame) &&
Plug_Export("ExecuteCommand", IRC_ExecuteCommand))
{
pCmd_AddCommand(COMMANDNAME);
Plug_Export("ConsoleLink", IRC_ConsoleLink);
if (!Plug_Export("ConExecuteCommand", IRC_ConExecuteCommand))
Con_TrySubPrint = Con_FakeSubPrint;
else
Con_TrySubPrint = pCon_SubPrint;
reloadconfig = true;
IRC_InitCvars();
if (BUILTINISVALID(Plug_GetNativePointer))
piceapi = pPlug_GetNativePointer(ICE_API_CURRENT);
return true;
}
else
{
pCon_Print("IRC Client Plugin failed\n");
}
return false;
}
qintptr_t IRC_ExecuteCommand(qintptr_t *args)
{
char cmd[256];
pCmd_Argv(0, cmd, sizeof(cmd));
if (!strcmp(cmd, COMMANDNAME))
{
ircclient_t *ircclient = ircclients;
char imsg[8192];
pCmd_Args(imsg, sizeof(imsg));
//FIXME: select an irc network more inteligently
IRC_Command(ircclient, ircclient?ircclient->defaultdest:"", imsg);
return true;
}
return false;
}
qintptr_t IRC_ConExecuteCommand(qintptr_t *args)
{
char buffer[256];
char imsg[8192];
ircclient_t *ircclient;
//FIXME: select the right network
pCmd_Argv(0, buffer, sizeof(buffer));
pCmd_Args(imsg, sizeof(imsg));
//buffer is something like: irc53:#foo
for (ircclient = ircclients; ircclient; ircclient = ircclient->next)
{
if (!strncmp(ircclient->id, buffer, strlen(ircclient->id)))
break;
}
if (!ircclient)
{
if (*buffer == '/')
IRC_Command(NULL, "", imsg);
else
Con_TrySubPrint(buffer, "You were disconnected\n");
return true;
}
IRC_Command(ircclient, buffer+strlen(ircclient->id), imsg);
return true;
}
static void IRC_AddClientMessage(ircclient_t *irc, char *msg)
{
char output[4096];
int len;
Q_strlcpy(output, msg, sizeof(output));
Q_strlcat(output, "\n", sizeof(output));
len = strlen(output);
if (irc->bufferedoutammount + len > sizeof(irc->bufferedoutmessage))
return;
memcpy(irc->bufferedoutmessage + irc->bufferedoutammount, output, len);
irc->bufferedoutammount += len;
if (irc_debug.value == 1) { IRC_Printf(irc, DEFAULTCONSOLE,COLOURYELLOW "<< %s \n",msg); }
}
static ircclient_t *IRC_FindAccount(const char *server)
{
ircclient_t *irc;
for (irc = ircclients; irc; irc = irc->next)
{
if (!strcmp(irc->server, server))
return irc;
}
return NULL; //no match
}
static ircclient_t *IRC_Create(const char *server, const char *nick, const char *realname, const char *hostname, const char *username, const char *password, const char *channels)
{
ircclient_t *irc;
//FIXME: accept server:+port for starttls
//FIXME: accept server:*port for initial tls
irc = IRC_Malloc(sizeof(ircclient_t));
if (!irc)
return NULL;
memset(irc, 0, sizeof(ircclient_t));
Q_snprintf(irc->id, sizeof(irc->id), "IRC%x%x:", rand(),rand());
irc->connecting = true;
irc->tlsmode = TLS_OFF;
irc->quitting = false;
irc->socket = invalid_handle;
Q_strlcpy(irc->server, server, sizeof(irc->server));
IRC_CvarUpdate();
Q_strlcpy(irc->primarynick, nick, sizeof(irc->primarynick));
Q_strlcpy(irc->nick, nick, sizeof(irc->nick));
Q_strlcpy(irc->realname, realname, sizeof(irc->realname));
Q_strlcpy(irc->hostname, hostname, sizeof(irc->hostname));
Q_strlcpy(irc->username, username, sizeof(irc->username));
Q_strlcpy(irc->pwd, password, sizeof(irc->pwd));
Q_strlcpy(irc->autochannels,channels, sizeof(irc->autochannels));
// gethostname(irc->hostname, sizeof(irc->hostname));
// irc->hostname[sizeof(irc->hostname)-1] = 0;
irc->next = ircclients;
ircclients = irc;
return irc;
}
static void IRC_SetPass(ircclient_t *irc, char *pass)
{
if (irc->pwd != pass)
Q_strlcpy(irc->pwd, pass, sizeof(irc->pwd));
if (*pass && irc->tlsmode != TLS_STARTING)
IRC_AddClientMessage(irc, va("PASS %s", pass));
}
static void IRC_SetNick(ircclient_t *irc, char *nick)
{
if (irc->nick != nick)
Q_strlcpy(irc->nick, nick, sizeof(irc->nick));
if (irc->tlsmode != TLS_STARTING)
IRC_AddClientMessage(irc, va("NICK %s", irc->nick));
}
static void IRC_SetUser(ircclient_t *irc, char *user)
{
IRC_CvarUpdate();
if (irc->tlsmode != TLS_STARTING)
{
const char *username = irc->username;
const char *realname = irc->realname;
if (!*username)
username = getenv("USER");
if (!username)
username = "FTE"; //we need something.
if (!*realname)
realname = username;
//servers will usually ignore the server arg, as they usually know their own dns name already...
//servers SHOULD ignore the hostname arg too (using a reverse dns). or they'll just replace it with an ip address (note: could use this instead of a STUN server).
//the username+realname are used, and need to be persistent for auto-op type mechanisms.
IRC_AddClientMessage(irc, va("USER %s %s %s :%s", username, irc->hostname, irc->server, realname));
}
}
static qboolean IRC_Establish(ircclient_t *irc)
{
if (!irc)
return false;
if (handleisvalid(irc->socket)) //don't need to do anything.
return true;
//clear up any stale state
irc->bufferedoutammount = 0;
irc->bufferedinammount = 0;
irc->quitting = false;
irc->socket = pNet_TCPConnect(irc->server, 6667); //port is only used if the url doesn't contain one. It's a default.
//not yet blocking. So no frequent attempts please...
//non blocking prevents connect from returning worthwhile sensible value.
if (!handleisvalid(irc->socket))
{
Con_Printf("IRC_OpenSocket: couldn't connect\n");
return false;
}
if (irc->tlsmode == TLS_INITIAL)
{
if (pNet_SetTLSClient(irc->socket, irc->server) < 0)
{
pNet_Close(irc->socket);
irc->socket = invalid_handle;
return false;
}
}
else if (irc->tlsmode != TLS_OFF)
{
IRC_AddClientMessage(irc, "STARTTLS");
irc->tlsmode = TLS_STARTING;
}
else
{
irc->nicktries = 0;
IRC_SetPass(irc, irc->pwd);
IRC_SetNick(irc, irc->nick);
IRC_SetUser(irc, irc_username.string);
}
return true;
}
static void IRC_ParseConfig(void)
{
qhandle_t config;
int len = pFS_Open("**plugconfig", &config, 1);
if (len >= 0)
{
char *buf = malloc(len+1);
char *msg = buf;
buf[len] = 0;
pFS_Read(config, buf, len);
pFS_Close(config);
while (msg && *msg)
{
ircclient_t *irc;
char server[256];
char channels[1024];
char nick[256];
char password[256];
char realname[256];
char hostname[256];
char username[256];
msg = COM_Parse(msg, server, sizeof(server));
msg = COM_Parse(msg, channels, sizeof(channels));
msg = COM_Parse(msg, nick, sizeof(nick));
msg = COM_Parse(msg, password, sizeof(password));
msg = COM_Parse(msg, realname, sizeof(realname));
msg = COM_Parse(msg, hostname, sizeof(hostname));
msg = COM_Parse(msg, username, sizeof(username));
if (*server)
{
irc = IRC_Create(server, nick, realname, hostname, username, password, channels);
if (irc)
{
irc->persist = true;
if (IRC_Establish(irc))
{
if (!*irc->autochannels)
IRC_Printf(irc, DEFAULTCONSOLE, "Trying to connect to %s\n", irc->server);
}
else
IRC_Printf(irc, DEFAULTCONSOLE, "Unable to connect to %s\n", irc->server);
}
}
}
free(buf);
}
}
static void IRC_WriteConfig(void)
{
qhandle_t config;
if (irc_config.value == 0)
return;
pFS_Open("**plugconfig", &config, 2);
if (config >= 0)
{
ircclient_t *irc;
for(irc = ircclients; irc; irc = irc->next)
{
char *s = va("\"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", irc->server, irc->autochannels, irc->primarynick, irc->pwd, irc->realname, irc->hostname, irc->username);
if (irc->quitting || !irc->persist)
continue;
pFS_Write(config, s, strlen(s));
}
pFS_Close(config);
}
}
static void IRC_MakeDefault(ircclient_t *irc)
{ //unlinks the client, then links it at the head, so that its the first found (thus the default)
ircclient_t **link;
for (link = &ircclients; *link; link = &(*link)->next)
{
if (*link == irc)
{
*link = irc->next;
break;
}
}
irc->next = ircclients;
ircclients = irc;
}
static void IRC_PartChannelInternal(ircclient_t *irc, char *channelname)
{
char ac[countof(irc->autochannels)];
char *chan;
strcpy(ac, irc->autochannels);
chan = strtok(ac, " ");
*irc->autochannels = 0;
while(chan)
{
if (*chan)
{
char *pwd = strchr(chan, ',');
if (pwd)
*pwd++ = 0;
if (strcmp(chan, channelname))
{
if (*irc->autochannels)
Q_strncatz(irc->autochannels, " ", sizeof(irc->autochannels));
if (pwd)
Q_strncatz(irc->autochannels, va("%s,%s", chan, pwd), sizeof(irc->autochannels));
else
Q_strncatz(irc->autochannels, va("%s", chan), sizeof(irc->autochannels));
}
}
chan = strtok(NULL, " ");
}
}
static void IRC_PartChannel(ircclient_t *irc, char *channelname)
{
IRC_PartChannelInternal(irc, channelname);
IRC_AddClientMessage(irc, va("PART %s", channelname));
}
static void IRC_JoinChannel(ircclient_t *irc, char *channel, char *key) // i screwed up, its actually: <channel>{,<channel>} [<key>{,<key>}]
{
IRC_PartChannelInternal(irc, channel);
if (*irc->autochannels)
Q_strncatz(irc->autochannels, " ", sizeof(irc->autochannels));
Q_strncatz(irc->autochannels, va("%s,%s", channel, key), sizeof(irc->autochannels));
if (key)
{
/*if (*channel != '#')
IRC_AddClientMessage(irc, va("JOIN #%s %s", channel,key));
else*/
IRC_AddClientMessage(irc, va("JOIN %s %s", channel,key));
}
else
{
/*if (*channel != '#')
IRC_AddClientMessage(irc, va("JOIN #%s", channel));
else*/
IRC_AddClientMessage(irc, va("JOIN %s", channel));
}
}
static void IRC_JoinChannels(ircclient_t *irc, char *channelstring)
{
char *chan = strtok(channelstring, " ");
while(chan)
{
if (*chan)
{
char *line = va("JOIN %s", chan);
char *comma = strchr(line, ',');
if (comma)
*comma = ' ';
IRC_AddClientMessage(irc, line);
}
chan = strtok(NULL, " ");
}
}
/*
ATTN: Spike
# (just for reference) == Ctrl+K in mirc to put the color code symbol in
now to have a background color, you must specify a forground color first (#0,15)
, denotes end of forground color, and start of background color
irc colors work in many strange ways:
#0-#15 for forground color // the code currently converts to this one, which is not the "proper" irc way, read the next one to understand. Still need to support it, just not output as it.
#00-#15 for forground color (note #010 to #015 is not valid) --- this is the "proper" irc way, because I could say "#11+1=2" (which means I want 1+1=2 to appear black (1), but instead it will come out as indigo (11) and look like this: +1=2)
background examples: (note
#0,15 (white forground, light gray background)
#00,15 (white forground, light gray background) // proper way
#15,0 (white forground, light gray background)
#15,00 (white forground, light gray background) // proper way
I hope this makes sense to you, to be able to edit the IRC_FilterMircColours function ~ Moodles
*/
static void IRC_FilterMircColours(char *msg)
{
int i;
int chars;
while(*msg)
{
if (*msg == 3)
{
chars = 2;
if (msg[1] >= '0' && msg[1] <= '9')
{
i = msg[1]- '0';
if (msg[2] >= '0' && msg[2] <= '9')
{
i = i*10 + (msg[2]-'0');
chars = 3;
}
}
else
i = msg[1];
switch(i)
{
case 0:
msg[1] = '7'; //white
break;
case 1:
msg[1] = '0'; //black
break;
case 2:
msg[1] = '4'; //darkblue
break;
case 3:
msg[1] = '2'; //darkgreen
break;
case 4:
msg[1] = '1'; //red
break;
case 5:
msg[1] = '1'; //brown
break;
case 6:
msg[1] = '5'; //purple
break;
case 7:
msg[1] = '3'; //orange
break;
case 8:
msg[1] = '3'; //yellow
break;
case 9:
msg[1] = '2'; //lightgreen
break;
case 10:
msg[1] = '6'; //darkcyan
break;
case 11:
msg[1] = '6'; //lightcyan
break;
case 12:
msg[1] = '4'; //lightblue
break;
case 13:
msg[1] = '5'; //pink
break;
case 14:
msg[1] = '7'; //grey
break;
case 15:
msg[1] = '7'; //lightgrey
break;
default:
msg++;
continue;
}
*msg = '^';
msg+=2;
if (chars==3)
memmove(msg, msg+1, strlen(msg));
continue;
}
msg++;
}
}
#define IRC_DONE 0
#define IRC_CONTINUE 1
#define IRC_KILL 2
static void magic_tokenizer(int word,char *thestring)
{
char *temp;
int i = 1;
strcpy(casevar[1],thestring);
temp = strchr(casevar[1], ' ');
while (i < 8)
{
i++;
if (temp != NULL)
{
strcpy(casevar[i],temp+1);
}
else
{
strcpy(casevar[i], "");
}
temp=strchr(casevar[i], ' ');
}
}
static void magic_etghack(char *thestring)
{
char *temp;
int i = 1;
strcpy(subvar[1],thestring);
temp = strchr(subvar[1], ' ');
while (i < 8)
{
i++;
if (temp != NULL)
{
strcpy(subvar[i],temp+1);
}
else
{
strcpy(subvar[i], "");
}
temp=strchr(subvar[i], ' ');
}
}
static void IRC_TryNewNick(ircclient_t *irc, char *nickname)
{
char *seedednick;
IRC_CvarUpdate();
if (irc->tlsmode == TLS_STARTING)
{
//don't submit any of this info here.
return;
}
if (irc->connecting)
{
if (irc->nicktries == 0)
{
irc->nicktries++;
if (*irc->primarynick && strcmp(nickname, irc->primarynick))
{
IRC_SetNick(irc, irc->primarynick);
return;
}
}
if (irc->nicktries == 1)
{
irc->nicktries++;
if (*irc_nick.string && strcmp(nickname, irc_nick.string))
{
IRC_SetNick(irc, irc_nick.string);
return;
}
}
if (irc->nicktries == 2)
{
irc->nicktries++;
if (*irc_altnick.string && strcmp(nickname, irc_altnick.string))
{
IRC_SetNick(irc, irc_altnick.string);
return;
}
}
if (++irc->nicktries == 10)
{
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: Unable to obtain usable nickname\n");
return;
}
//panic and pick something at random
//IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: primary nickname in use. Attempting random nickname.\n");
if (*irc->primarynick && irc->nicktries < 7)
seedednick = va("%.6s%i", irc->primarynick, rand());
else if (*irc_nick.string && irc->nicktries < 8)
seedednick = va("%.6s%i", irc_nick.string, rand());
else if (*irc_altnick.string && irc->nicktries < 9)
seedednick = va("%.6s%i", irc_altnick.string, rand());
else
seedednick = va("%.6s%i", "FTE", rand());
seedednick[9] = 0; //'Each client is distinguished from other clients by a unique nickname having a maximum length of nine (9) characters'
IRC_SetNick(irc, seedednick);
}
}
//==================================================
static void numbered_command(int comm, char *msg, ircclient_t *irc) // move vars up 1 more than debug says
{
magic_tokenizer(0,msg);
switch (comm)
{
case 1: /* RPL_WELCOME */
case 2: /* RPL_YOURHOST */
case 3: /* RPL_CREATED */
case 4: /* RPL_MYINFO */
case 5: /* RPL_ISUPPORT */
{
if (irc->tlsmode != TLS_STARTING)
irc->connecting = 0; // ok we are connected
if (irc_motd.value)
IRC_Printf(irc, DEFAULTCONSOLE, COLOURYELLOW "SERVER STATS: %s\n",casevar[3]);
return;
}
// case 5: /* RPL_BOUNCE */
// {
// IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "RPL_BOUNCE: %s\n",casevar[3]);
// return;
// }
case 20: /* RPL_HELLO */
{
if (irc_motd.value)
IRC_Printf(irc, DEFAULTCONSOLE, COLOURYELLOW "%s\n",casevar[3]);
return;
}
case 42: /*RPL_YOURID */
{
if (irc_motd.value)
IRC_Printf(irc, DEFAULTCONSOLE, COLOURYELLOW "%s\n",casevar[3]);
return;
}
case 250:
case 251: /* RPL_LUSERCLIENT */
case 252: /* RPL_LUSEROP */
case 253: /* RPL_LUSERUNKNOWN */
case 254: /* RPL_LUSERCHANNELS */
case 255: /* RPL_LUSERME */
case 256: /* RPL_ADMINME */
case 257: /* RPL_ADMINLOC1 */
case 258: /* RPL_ADMINLOC2 */
case 259: /* RPL_ADMINEMAIL */
case 265:
case 266:
{
if (irc_motd.value)
IRC_Printf(irc, DEFAULTCONSOLE, COLOURYELLOW "SERVER STATS: %s\n",casevar[3]);
return;
}
case 301: /* #define RPL_AWAY 301 */
{
char *username = strtok(casevar[3], " ");
char *awaymessage = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (Away Message: %s)\n",username,awaymessage);
return;
}
case 305: /* RPL_UNAWAY */
case 306: /* RPL_NOWAWAY */
{
char *away = casevar[3]+1;
IRC_Printf(irc, CURRENTCONSOLE,"%s\n",away);
return;
}
case 311: /* #define RPL_WHOISUSER 311 */
{
char *username = strtok(casevar[3], " ");
char *ident = strtok(casevar[4], " ");
char *address = strtok(casevar[5], " ");
char *realname = casevar[7]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (Ident: %s) (Address: %s) (Realname: %s) \n", username, ident, address, realname);
return;
}
case 312: /* #define RPL_WHOISSERVER 312 */ //seems to be /whowas also
{
char *username = strtok(casevar[3], " ");
char *serverhostname = strtok(casevar[4], " ");
char *servername = casevar[5]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (Server: %s) (Server Name: %s) \n", username, serverhostname, servername);
return;
}
case 313: /* RPL_WHOISOPERATOR */
{
char *username = strtok(casevar[3], " ");
char *isoperator = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (%s)\n", username,isoperator);
return;
}
case 317: /* #define RPL_WHOISIDLE 317 */
{
char *username = strtok(casevar[3], " ");
char *secondsidle = strtok(casevar[4], " ");
char *signontime = strtok(casevar[5], " ");
time_t t;
const struct tm *tm;
char buffer[100];
t=strtoul(signontime, 0, 0);
tm=localtime(&t);
strftime (buffer, 100, "%a %b %d %H:%M:%S", tm);
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (Idle Time: %s seconds) (Signon Time: %s) \n", username, secondsidle, buffer);
return;
}
case 318: /* #define RPL_ENDOFWHOIS 318 */
{
char *endofwhois = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: %s\n", endofwhois);
return;
}
case 319: /* #define RPL_WHOISCHANNELS 319 */
{
char *username = strtok(casevar[3], " ");
char *channels = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE,"WHOIS: <%s> (Channels: %s)\n",username,channels); // need to remove the space from the end of channels
return;
}
case 321:
{
// IRC_Printf(irc, "list", "Start /LIST\n");
return;
}
case 322: /* #define RPL_LIST 322 */
{
char *channel = strtok(casevar[3], " ");
char *users = strtok(casevar[4], " ");
char *topic = casevar[5]+1;
IRC_Printf(irc, "list", "^1Channel:^7 %s ^1Users:^7 %s ^1Topic:^7 %s\n\n", channel,users,topic);
return;
}
case 323: /* RPL_LISTEND*/
{
//char *endoflist = casevar[3]+1;
// IRC_Printf(irc, "list", "%s\n",endoflist);
return;
}
case 333: /* RPL_TOPICWHOTIME channel user timestamp*/
return;
case 366: /* RPL_ENDOFNAMES */
{
char *channel = strtok(casevar[3], " ");
char *endofnameslist = casevar[4]+1;
IRC_Printf(irc, channel,"%s\n",endofnameslist);
return;
}
case 372: /* RPL_MOTD */
case 375: /* RPL_MOTDSTART */
case 376: /* RPL_ENDOFMOTD */
{
char *motdmessage = casevar[3]+1;
IRC_CvarUpdate();
if (irc_motd.value == 2)
IRC_Printf(irc, DEFAULTCONSOLE, "MOTD: %s\n", motdmessage);
else if (irc_motd.value)
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", motdmessage);
if (*irc->autochannels)
IRC_JoinChannels(irc, irc->autochannels);
return;
}
case 378:
{
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", msg);
return;
}
case 401: /* ERR_NOSUCHNICK */
case 403: /* ERR_NOSUCHCHANNEL */
case 404: /* ERR_CANNOTSENDTOCHAN */
case 405: /* ERR_TOOMANYCHANNELS */
case 442: /* ERR_NOTONCHANNEL */
{
char *username = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, username, COLOURRED "ERROR <%s>: %s\n",username,error);
return;
}
case 432: /* #define ERR_ERRONEUSNICKNAME 432 */
{
IRC_Printf(irc, DEFAULTCONSOLE, "Erroneous/invalid nickname given\n");
IRC_TryNewNick(irc, "FTEUser");
return;
}
case 433: /* #define ERR_NICKNAMEINUSE 433 */
case 438:
case 453:
{
char *nickname = strtok(casevar[4], " ");
char *badnickname = ":Nickname";
if ( !strcasecmp(nickname,badnickname) ) // bug with ircd, the nickname actually shifts position.
{
nickname = strtok(casevar[3], " ");
}
// IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s> is already in use.\n",nickname);
IRC_TryNewNick(irc, nickname);
return;
}
case 471: /* ERR_CHANNELISFULL */
{
char *channel = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (Channel is full and has reached user limit)\n",channel,error);
return;
}
case 472: /* ERR_UNKNOWNMODE */
{
char *mode = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (Unknown mode)\n",mode,error);
return;
}
case 473: /* ERR_INVITEONLYCHAN */
{
char *channel = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (Invite only)\n",channel,error);
return;
}
case 474: /* ERR_BANNEDFROMCHAN */
{
char *channel = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (You are banned)\n",channel,error);
return;
}
case 475: /* ERR_BADCHANNELKEY */
{
char *channel = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (Need the correct channel key. Example: /join %s bananas)\n",channel,error,channel);
return;
}
case 482: /* ERR_CHANOPRIVSNEEDED */
{
char *channel = strtok(casevar[3], " ");
char *error = casevar[4]+1;
IRC_Printf(irc, DEFAULTCONSOLE, COLOURRED "ERROR: <%s>: %s (Need +o or @ status)\n",channel,error);
return;
}
case 670: /* RPL_STARTTLS */
{
pNet_SetTLSClient(irc->socket, irc->server);
irc->tlsmode = TLS_START;
irc->nicktries = 0;
IRC_SetPass(irc, irc->pwd);
IRC_SetNick(irc, irc->nick);
IRC_SetUser(irc, irc_username.string);
return;
}
case 691: /* ERR_STARTTLS */
{
IRC_Printf(irc, DEFAULTCONSOLE, COLOURYELLOW "STARTTLS Failed: %s\n", casevar[3]);
pNet_Close(irc->socket);
irc->socket = invalid_handle;
return;
}
}
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", msg); // if no raw number exists, print the thing
}
static struct ircice_s *IRC_ICE_Find(ircclient_t *irc, const char *sender, enum iceproto_e type)
{
struct ircice_s *ice;
for (ice = irc->ice; ice; ice = ice->next)
{
if (ice->type == type && !strcmp(ice->peer, sender))
return ice;
}
return NULL;
}
static struct ircice_s *IRC_ICE_Create(ircclient_t *irc, const char *sender, enum iceproto_e type, qboolean creator)
{
struct icestate_s *ice;
struct ircice_s *ircice;
if (!piceapi)
return NULL;
if (!creator && type == ICEP_QWSERVER)
ice = piceapi->ICE_Create(NULL, NULL, sender, ICEM_ICE, ICEP_QWCLIENT);
else if (!creator && type == ICEP_QWCLIENT)
ice = piceapi->ICE_Create(NULL, NULL, sender, ICEM_ICE, ICEP_QWSERVER);
else
ice = piceapi->ICE_Create(NULL, NULL, sender, ICEM_ICE, type);
if (!ice)
return NULL;
piceapi->ICE_Set(ice, "controller", creator?"1":"0");
if (creator && type == ICEP_VOICE)
{
//note: the engine will ignore codecs it does not support.
piceapi->ICE_Set(ice, "codec96", "opus@48000");
piceapi->ICE_Set(ice, "codec97", "speex@16000"); //wide
piceapi->ICE_Set(ice, "codec98", "speex@8000"); //narrow
piceapi->ICE_Set(ice, "codec99", "speex@32000"); //ultrawide
piceapi->ICE_Set(ice, "codec8", "pcma@8000");
piceapi->ICE_Set(ice, "codec0", "pcmu@8000");
}
//query dns to see if there's a stunserver hosted by the same domain
//nslookup -querytype=SRV _stun._udp.example.com
// Q_snprintf(stunhost, sizeof(stunhost), "_stun._udp.%s", ice->server);
// if (NET_DNSLookup_SRV(stunhost, stunhost, sizeof(stunhost)))
// piceapi->ICE_Set(ice, "stunip", stunhost);
// else
{
//irc services tend to not provide any stun info, so steal someone's... hopefully they won't mind too much. :(
piceapi->ICE_Set(ice, "stunport", "19302");
piceapi->ICE_Set(ice, "stunip", "stun.l.google.com");
}
ircice = malloc(sizeof(*ircice));
memset(ircice, 0, sizeof(*ircice));
ircice->next = irc->ice;
irc->ice = ircice;
ircice->type = type;
Q_strlcpy(ircice->peer, sender, sizeof(ircice->peer));
ircice->host = creator;
ircice->accepted = false;
ircice->allowed = creator;
ircice->ice = ice;
return ircice;
}
static void IRC_ICE_Update(ircclient_t *irc, struct ircice_s *ice, char updatetype)
{
//I was originally using colons to separate terms, but switched to slashes to avoid smilies for irc clients that print unknown CTCP messages.
char message[1024];
struct icecandinfo_s *c;
char *icetype;
if (!ice->allowed && updatetype != '-')
return;
*message = 0;
switch(ice->type)
{
default:
case ICEP_VOICE:
icetype = "voice";
break;
case ICEP_QWSERVER:
icetype = "qwserver";
break;
case ICEP_QWCLIENT:
icetype = "qwclient";
break;
}
if (updatetype == '=' || updatetype == '*')
{
char ufrag[256];
char pwd[256];
piceapi->ICE_Get(ice->ice, "lufrag", ufrag, sizeof(ufrag));
piceapi->ICE_Get(ice->ice, "lpwd", pwd, sizeof(pwd));
Q_snprintf(message, sizeof(message), " ufrag/%s pwd/%s", ufrag, pwd);
}
if (updatetype == '+' || updatetype == '=')
{
unsigned int i;
for (i = 0; i <= 127; i++)
{
char codec[256];
char codecname[64];
char argn[64];
Q_snprintf(argn, sizeof(argn), "codec%i", i);
if (!piceapi->ICE_Get(ice->ice, argn, codecname, sizeof(codecname)))
continue;
if (!strcmp(codecname, "speex@8000")) //speex narrowband
Q_snprintf(codec, sizeof(codec), "codec/%i/speex/8000", i);
else if (!strcmp(codecname, "speex@16000")) //speex wideband
Q_snprintf(codec, sizeof(codec), "codec/%i/speex/16000", i);
else if (!strcmp(codecname, "speex@32000")) //speex ultrawideband
Q_snprintf(codec, sizeof(codec), "codec/%i/speex/32000", i);
else if (!strcmp(codecname, "pcma@8000")) //speex wideband
Q_snprintf(codec, sizeof(codec), "codec/%i/pcma/8000", i);
else if (!strcmp(codecname, "pcmu@8000")) //speex ultrawideband
Q_snprintf(codec, sizeof(codec), "codec/%i/pcmu/8000", i);
else if (!strcmp(codecname, "opus@48000")) //opus codec.
Q_snprintf(codec, sizeof(codec), "codec/%i/opus/48000", i);
else
continue;
if (strlen(message) + strlen(codec) + 2 > 256)
{
IRC_AddClientMessage(irc, va("NOTICE %s :\001FTEICE %c%s%s\001", ice->peer, updatetype, icetype, message));
updatetype = '%';
*message = 0;
}
Q_strlcat(message, " ", sizeof(message));
Q_strlcat(message, codec, sizeof(message));
}
}
/* if (*message)
{
IRC_AddClientMessage(irc, va("NOTICE %s :\001FTEICE %c%s%s\001", ice->peer, updatetype, icetype, message));
*message = 0;
}
*/
if (updatetype != '+' && updatetype != '-')
{
while ((c = piceapi->ICE_GetLCandidateInfo(ice->ice)))
{
char type[] = "hspr";
char cand[256];
Q_snprintf(cand, sizeof(cand), "cand/"
"%c%c/%i/%i/"
"%i/%i/%i/"
"%i/%s/%s",
type[c->type], 'u', c->priority, c->port,
c->network, c->generation, c->foundation,
c->component, c->candidateid, c->addr);
if (strlen(message) + strlen(cand) + 2 > 256)
{
IRC_AddClientMessage(irc, va("NOTICE %s :\001FTEICE %c%s%s\001", ice->peer, updatetype, icetype, message));
updatetype = '%';
*message = 0;
}
Q_strlcat(message, " ", sizeof(message));
Q_strlcat(message, cand, sizeof(message));
}
}
if (*message || updatetype != '%')
IRC_AddClientMessage(irc, va("NOTICE %s :\001FTEICE %c%s%s\001", ice->peer, updatetype, icetype, message));
}
static void IRC_ICE_ParseCandidate(struct icestate_s *ice, char *cand)
{
char *addr;
struct icecandinfo_s info;
if (strlen(cand) < 12)
return;
switch(cand[5])
{
case 'h': info.type = ICE_HOST; break;
case 's': info.type = ICE_SRFLX; break;
case 'p': info.type = ICE_PRFLX; break;
default:
case 'r': info.type = ICE_RELAY; break;
}
info.transport = (cand[6] == 't')?1:0;
info.priority = strtol(cand+8, &cand, 0); if (*cand != '/')return;
info.port = strtol(cand+1, &cand, 0); if (*cand != '/')return;
info.network = strtol(cand+1, &cand, 0); if (*cand != '/')return;
info.generation = strtol(cand+1, &cand, 0); if (*cand != '/')return;
info.foundation = strtol(cand+1, &cand, 0); if (*cand != '/')return;
info.component = strtol(cand+1, &cand, 0); if (*cand != '/')return;
addr = strchr(cand+1, '/');
if (!addr)
return;
*addr++ = 0;
Q_strlcpy(info.candidateid, cand+1, sizeof(info.candidateid));
Q_strlcpy(info.addr, addr, sizeof(info.candidateid));
piceapi->ICE_AddRCandidateInfo(ice, &info);
}
static void IRC_ICE_ParseCodec(struct icestate_s *ice, char *codec)
{
char *start;
unsigned int num;
char name[64];
unsigned int rate;
num = strtoul(codec+6, &codec, 0); if (*codec != '/')return;
start = codec+1; codec = strchr(codec, '/'); if (!codec)return;*codec = 0; Q_strlcpy(name, start, sizeof(name));
rate = strtoul(codec+1, &codec, 0);
Q_strlcat(name, va("@%u", rate), sizeof(name));
piceapi->ICE_Set(ice, va("codec%i", num), name);
}
static void IRC_ICE_Parse(ircclient_t *irc, const char *sender, char *message)
{
struct ircice_s *ice;
char token[256];
enum iceproto_e type = ICEP_INVALID;
message = COM_Parse(message, token, sizeof(token));
if (*token == '+' || *token == '=' || *token == '*' || *token == '%')
{ //+ is offer or accept for a new content type
//= is an ack from the receiver
//* is the final handshake that includes offerer's full details
//% is extra updates.
char icetype = *token;
if (!strcmp(token+1, "voice"))
type = ICEP_VOICE;
else if (!strcmp(token+1, "qwserver"))
type = ICEP_QWSERVER;
else if (!strcmp(token+1, "qwclient"))
type = ICEP_QWCLIENT;
else
{
IRC_Printf(irc, sender, "ICE session type %s is not recognised\n", token);
return;
}
ice = IRC_ICE_Find(irc, sender, type);
if (!ice && (icetype == '+' || icetype == '='))
ice = IRC_ICE_Create(irc, sender, type, false);//icetype=='=');
if (ice)
{
while(message)
{
message = COM_Parse(message, token, sizeof(token));
if (!strncmp(token, "cand/", 5))
IRC_ICE_ParseCandidate(ice->ice, token);
else if (!strncmp(token, "codec/", 6))
IRC_ICE_ParseCodec(ice->ice, token);
else if (!strncmp(token, "ufrag/", 6))
piceapi->ICE_Set(ice->ice, "rufrag", token+6);
else if (!strncmp(token, "pwd/", 4))
piceapi->ICE_Set(ice->ice, "rpwd", token+4);
else if (*token)
IRC_Printf(irc, sender, "unknown ice token %s\n", token);
}
if ((icetype == '=' || icetype == '*') && !ice->accepted && ice->allowed)
{
piceapi->ICE_Set(ice->ice, "state", STRINGIFY(ICE_CONNECTING));
ice->accepted = true;
}
switch(icetype)
{
case '+':
//needs user
if (!ice->allowed)
{
switch(type)
{
case ICEP_VOICE: IRC_Printf(irc, sender, "%s is trying to call you. ^[[Click to Converse]\\act\\iceaccept_v\\who\\%s^] ^[[Click to Decline]\\act\\icedecline_v\\who\\%s^] \n", sender, sender, sender); break;
case ICEP_QWSERVER: IRC_Printf(irc, sender, "%s wants you to join their game. ^[[Click to Join]\\act\\iceaccept_s\\who\\%s^] ^[[Click to Decline]\\act\\icedecline_s\\who\\%s^] \n", sender, sender, sender); break;
case ICEP_QWCLIENT: IRC_Printf(irc, sender, "%s is trying to gatecrash your game. ^[[Click to Allow]\\act\\iceaccept_c\\who\\%s^] ^[[Click to Decline]\\act\\icedecline_c\\who\\%s^] \n", sender, sender, sender); break;
case ICEP_INVALID: break;
case ICEP_VIDEO: break;
}
}
else
{
switch(type)
{
case ICEP_VOICE: IRC_Printf(irc, sender, "Accepting voice call\n"); break;
case ICEP_QWSERVER: IRC_Printf(irc, sender, "Accepting game invite\n"); break;
case ICEP_QWCLIENT: IRC_Printf(irc, sender, "Accepting gatecrash\n"); break;
case ICEP_INVALID: break;
case ICEP_VIDEO: break;
}
IRC_ICE_Update(irc, ice, '=');
}
break;
case '=':
switch(type)
{
case ICEP_VOICE: IRC_Printf(irc, sender, "Establishing voice call\n"); break;
case ICEP_QWSERVER: IRC_Printf(irc, sender, "Establishing game invite\n"); break;
case ICEP_QWCLIENT: IRC_Printf(irc, sender, "Establishing gatecrash\n"); break;
case ICEP_INVALID: break;
case ICEP_VIDEO: break;
}
IRC_ICE_Update(irc, ice, '*');
break;
default:
switch(type)
{
case ICEP_VOICE: IRC_Printf(irc, sender, "Updating voice call\n"); break;
case ICEP_QWSERVER: IRC_Printf(irc, sender, "Updating game invite\n"); break;
case ICEP_QWCLIENT: IRC_Printf(irc, sender, "Updating gatecrash\n"); break;
case ICEP_INVALID: break;
case ICEP_VIDEO: break;
}
IRC_ICE_Update(irc, ice, '%');
break;
}
}
}
else if (*token == '-')
{
IRC_Printf(irc, sender, "dropping connections is not supported yet: %s\n", token);
}
else
IRC_Printf(irc, sender, "ICE command type not supported: %s\n", token);
}
static void IRC_ICE_Frame(ircclient_t *irc)
{
char bah[8];
struct ircice_s *ice;
for (ice = irc->ice; ice; ice = ice->next)
{
if (!ice->accepted || !ice->allowed)
continue;
//ice needs some maintainence. if things change then we need to be prepared to send updated candidate info
piceapi->ICE_Get(ice->ice, "newlc", bah, sizeof(bah));
if (atoi(bah))
{
IRC_Printf(irc, ice->peer, "Sending updated peer info\n");
IRC_ICE_Update(irc, ice, '%');
}
//FIXME: detect when the ice connection goes idle.
}
}
static void IRC_ICE_Authorise(ircclient_t *irc, const char *with, enum iceproto_e type, qboolean authorize, char *announce)
{
struct ircice_s *ice, **link;
for (link = &irc->ice; *link; link = &(*link)->next)
{
ice = *link;
if (ice->type == type)
if (!strcmp(ice->peer, with))
{
if (authorize)
{
if (ice->allowed)
{
IRC_Printf(irc, announce, "Connection is already authorised\n");
return; //nothing to do
}
//yay! its good to go!
ice->allowed = true;
switch(type)
{
case ICEP_VOICE: IRC_Printf(irc, announce, "Accepting voice call\n"); break;
case ICEP_QWSERVER: IRC_Printf(irc, announce, "Accepting game invite\n"); break;
case ICEP_QWCLIENT: IRC_Printf(irc, announce, "Accepting gatecrash\n"); break;
case ICEP_VIDEO: break;
case ICEP_INVALID: break;
}
IRC_ICE_Update(irc, ice, '=');
}
else
{
IRC_ICE_Update(irc, ice, '-');
*link = ice->next;
if (ice->ice)
piceapi->ICE_Close(ice->ice);
IRC_Free(ice);
IRC_Printf(irc, announce, "Connection terminated\n");
}
return;
}
}
IRC_Printf(irc, announce, "Connection is already terminated\n");
}
qintptr_t IRC_ConsoleLink(qintptr_t *args)
{
ircclient_t *irc;
char link[256];
char *who = NULL;
char *channel = NULL;
char what[256];
char whobuf[256];
char which[512];
enum iceproto_e type;
// pCmd_Argv(0, text, sizeof(text));
pCmd_Argv(1, link, sizeof(link));
pCmd_Argv(2, which, sizeof(which));
Plug_Info_ValueForKey(link, "act", what, sizeof(what));
who = Plug_Info_ValueForKey(link, "who", whobuf, sizeof(whobuf));
for (irc = ircclients; irc; irc = irc->next)
{
if (!strncmp(irc->id, which, strlen(irc->id)))
{
channel = which + strlen(irc->id);
if (!*who)
who = channel;
break;
}
}
if (!irc || !who || !*what)
return false;
if (!strcmp(what, "reconnect"))
{
if (handleisvalid(irc->socket))
IRC_Printf(irc, channel, "Already %s.\n", irc->connecting?"reconnecting":"connected");
else if (IRC_Establish(irc))
IRC_Printf(irc, channel, "Reconnecting...\n");
else
IRC_Printf(irc, channel, "Unable to connect\n");
return true;
}
if (!*who)
return false; //that seems wrong. probably nothing to do with irc.
if (irc->tlsmode == TLS_STARTING || irc->connecting)
{
IRC_SetFooter(irc, channel, "Still connecting. Please wait.\n");
return true;
}
if (!strcmp(what, "iceaccept_v") || !strcmp(what, "iceaccept_s") || !strcmp(what, "iceaccept_c"))
{
switch(what[10])
{default:
case 'v': type = ICEP_VOICE; break;
case 's': type = ICEP_QWSERVER; break;
case 'c': type = ICEP_QWCLIENT; break;
}
IRC_Printf(irc, channel, "Accepting foo from %s\n", who);
IRC_ICE_Authorise(irc, who, type, true, channel);
return true;
}
else if (!strcmp(what, "icedecline_v") || !strcmp(what, "icedecline_s") || !strcmp(what, "icedecline_c"))
{
switch(what[11])
{default:
case 'v': type = ICEP_VOICE; break;
case 's': type = ICEP_QWSERVER; break;
case 'c': type = ICEP_QWCLIENT; break;
}
IRC_ICE_Authorise(irc, who, type, false, channel);
return true;
}
else if (!strcmp(what, "user"))
{
char links[2048];
char link[512];
Q_snprintf(links, sizeof(links), "%s:", who);
if (1)
{
Q_snprintf(link, sizeof(link), " ^[[Message]\\act\\msg\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
if (IRC_ICE_Find(irc, who, ICEP_VOICE))
{
Q_snprintf(link, sizeof(link), " ^[[Hang up]\\act\\icedecline_v\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
else
{
Q_snprintf(link, sizeof(link), " ^[[Call]\\act\\icestart_v\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
if (IRC_ICE_Find(irc, who, ICEP_QWSERVER))
{
Q_snprintf(link, sizeof(link), " ^[[Disconnect]\\act\\icedecline_s\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
else
{
Q_snprintf(link, sizeof(link), " ^[[Invite]\\act\\icestart_s\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
if (IRC_ICE_Find(irc, who, ICEP_QWCLIENT))
{
Q_snprintf(link, sizeof(link), " ^[[Disconnect]\\act\\icedecline_c\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
else
{
Q_snprintf(link, sizeof(link), " ^[[Gatecrash]\\act\\icestart_c\\who\\%s^]", who);
Q_strlcat(links, link, sizeof(links));
}
IRC_SetFooter(irc, channel, links);
return true;
}
else if (!strcmp(what, "msg"))
{
IRC_Printf(irc, who, "");
return true;
}
else if (!strcmp(what, "icestart_v") || !strcmp(what, "icestart_s") || !strcmp(what, "icestart_c"))
{
struct ircice_s *ice;
char *text;
char link[512];
switch(what[9])
{default:
case 'v': type = ICEP_VOICE; text = "Calling"; break;
case 's': type = ICEP_QWSERVER; text = "Inviting"; break;
case 'c': type = ICEP_QWCLIENT; text = "Gatecrashing"; break;
}
ice = IRC_ICE_Create(irc, who, type, true);
if (ice)
{
Q_snprintf(link, sizeof(link), "^["COLOURGREEN"%s\\act\\user^]", ice->peer);
IRC_ICE_Update(irc, ice, '+');
IRC_Printf(irc, ice->peer, "<%s %s ^[[Abort]\\act\\icedecline_%c\\who\\%s^]>\n", text, link, what[9], ice->peer);
}
return true;
}
return false;
}
//==================================================
static int IRC_ClientFrame(ircclient_t *irc)
{
char prefix[256];
int ret;
char *nextmsg, *msg;
char *temp;
char token[1024];
char var[9][1000];
int i = 1;
ret = pNet_Recv(irc->socket, irc->bufferedinmessage+irc->bufferedinammount, sizeof(irc->bufferedinmessage)-1 - irc->bufferedinammount);
if (ret == 0)
{
if (!irc->bufferedinammount) //if we are half way through a message, read any possible conjunctions.
return IRC_DONE; //remove
}
if (ret < 0)
return IRC_KILL;
if (ret>0)
irc->bufferedinammount+=ret;
irc->bufferedinmessage[irc->bufferedinammount] = '\0';
nextmsg = strstr(irc->bufferedinmessage, "\r\n");
if (!nextmsg)
return IRC_DONE;
*nextmsg = '\0';
nextmsg+=2;
msg = irc->bufferedinmessage;
strcpy(var[1],msg);
temp = strchr(var[1], ' ');
while (i < 8)
{
i++;
if (temp != NULL)
{
strcpy(var[i],temp+1);
}
else
{
strcpy(var[i], "");
}
temp=strchr(var[i], ' ');
}
IRC_CvarUpdate(); // is this the right place for it?
// if (irc_debug.value == 1) { IRC_Printf(irc, DEFAULTCONSOLE,COLOURRED "!!!!! ^11: %s ^22: %s ^33: %s ^44: %s ^55: %s ^66: %s ^77: %s ^88: %s\n",var[1],var[2],var[3],var[4],var[5],var[6],var[7],var[8]); }
if (irc_debug.value == 1) { IRC_Printf(irc, DEFAULTCONSOLE,COLOURRED "%s\n",var[1]); }
if (*msg == ':') //we need to strip off the prefix
{
char *sp = strchr(msg, ' ');
if (!sp)
{
IRC_Printf(irc, DEFAULTCONSOLE, "Ignoring bad message\n%s\n", msg);
memmove(irc->bufferedinmessage, nextmsg, irc->bufferedinammount - (msg-irc->bufferedinmessage));
irc->bufferedinammount-=nextmsg-irc->bufferedinmessage;
return IRC_CONTINUE;
}
if (sp-msg >= sizeof(prefix))
Q_strlcpy(prefix, msg+1, sizeof(prefix));
else
Q_strlcpy(prefix, msg+1, sp-msg);
msg = sp;
while(*msg == ' ')
msg++;
}
else
strcpy(prefix, irc->server);
if (!strncmp(var[1], "NOTICE AUTH ", 12))
{
IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "SERVER NOTICE: %s\n", var[3]+1);
}
else if (!strncmp(var[1], "PING ", 5))
{
IRC_AddClientMessage(irc, va("PONG %s", var[2]));
}
else if (!strncmp(var[2], "NOTICE ", 6))
{
char *exc = strchr(prefix, '!');
char *col = strchr(msg+6, ':');
char *end;
char *to = msg + 7;
char *etghack;
if (!strncmp(var[4]+1, "\1", 1))
{
char delimiters[] = "!";
char *username = strtok(var[1]+1, delimiters);
char *ctcpreplytype = strtok(var[4]+2, " ");
char *ctcpreply = var[5];
if (!strcmp(ctcpreplytype, "FTEICE"))
{
IRC_Printf(irc, username, "ICE from %s\n", username); //from client
IRC_ICE_Parse(irc, username, ctcpreply);
}
else
IRC_Printf(irc, DEFAULTCONSOLE,"<CTCP Reply> %s FROM %s: %s\n",ctcpreplytype,username,ctcpreply); // need to remove the last char on the end of ctcpreply
}
else if (exc && col)
{
*col = '\0';
col++;
while(*to <= ' ' && *to)
to++;
for (end = to + strlen(to)-1; end >= to && *end <= ' '; end--)
*end = '\0';
if (!strcmp(to, irc_nick.string))
to = prefix; //This was directed straight at us.
//So change the 'to', to the 'from'.
for (end = to; *end; end++)
{
if (*end >= 'A' && *end <= 'Z')
*end = *end + 'a' - 'A';
}
*exc = '\0';
if (!strncmp(col, "\001", 1))
{
end = strchr(col+1, '\001');
if (end)
*end = '\0';
if (!strncmp(col+1, "ACTION ", 7))
{
IRC_FilterMircColours(col+8);
IRC_Printf(irc, to, COLOURGREEN "***%s "COLORWHITE"%s\n", prefix, col+8); //from client
}
}
else
{
IRC_FilterMircColours(col);
IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "NOTICE: -%s- %s\n", prefix, col); //from client
}
}
else
{
etghack = strtok(var[1],"\n");
if (!irc->connecting || IRC_WindowShown(irc, DEFAULTCONSOLE))
IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "SERVER NOTICE: <%s> %s\n", prefix, etghack);
// strcpy(servername,prefix);
while (1)
{
etghack = strtok(NULL, "\n");
if (etghack == NULL)
break;
magic_etghack(etghack);
if (atoi(subvar[2]) != 0)
numbered_command(atoi(subvar[2]), etghack, irc);
else
IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "SERVER NOTICE: <%s> %s\n", prefix, subvar[4]);
}
}
}
else if (!strncmp(var[2], "PRIVMSG ", 7)) //no autoresponses to notice please, and any autoresponses should be in the form of a notice
{
char *exc = strchr(prefix, '!');
char *col = strchr(msg+6, ':');
char *end;
char *to = msg + 7;
//message takes the form :FROM PRIVMSG TO :MESSAGE
if (BUILTINISVALID(LocalSound))
pLocalSound ("misc/talk.wav");
if ((!strcasecmp(var[4]+1, "\1VERSION\1")) && (!strncmp(var[2], "PRIVMSG ", 7)))
{
char *username;
char delimiters[] = "!";
username = strtok(var[1]+1, delimiters);
IRC_AddClientMessage(irc, va("NOTICE %s :\1VERSION FTEQW-IRC-Plugin Release: %s", username, RELEASE));
}
else if ((!strcasecmp(var[4]+1, "\1TIME\1")) && (!strncmp(var[2], "PRIVMSG ", 7)))
{
char delimiters[] = "!";
char *username = strtok(var[1], delimiters);
time_t t;
const struct tm *tm;
char buffer[100];
time(&t);
tm=localtime(&t);
strftime (buffer, 100, "%a %b %d %H:%M:%S", tm);
IRC_AddClientMessage(irc, va("NOTICE %s :\1TIME %s\1", username, buffer));
}
else if (exc && col)
{
char link[256];
*col = '\0';
col++;
while(*to <= ' ' && *to)
to++;
for (end = to + strlen(to)-1; end >= to && *end <= ' '; end--)
*end = '\0';
if (!strcmp(to, irc->nick))
to = prefix; //This was directed straight at us.
//So change the 'to', to the 'from'.
for (end = to; *end; end++)
{
if (*end >= 'A' && *end <= 'Z')
*end = *end + 'a' - 'A';
}
*exc = '\0';
//a link to interact with the sender
Q_snprintf(link, sizeof(link), "^["COLOURGREEN"%s\\act\\user\\who\\%s^]", prefix, prefix);
if (!strncmp(col, "\001", 1))
{
end = strchr(col+1, '\001');
if (end)
*end = '\0';
if (!strncmp(col+1, "ACTION ", 7))
{
IRC_FilterMircColours(col+8);
IRC_Printf(irc, to, "***%s %s\n", link, col+8); //from client
}
else if (!strncmp(col+1, "PING ", 5))
{
time_t currentseconds;
currentseconds = time (NULL);
IRC_Printf(irc, to, "CTCP Ping from %s\n", link); //from client
IRC_AddClientMessage(irc, va("NOTICE %s :\001PING %u\001\r\n", prefix, (unsigned int)currentseconds));
}
else if (!strncmp(col+1, "VERSION ", 8))
{
IRC_Printf(irc, to, "CTCP Version from %s\n", link); //from client
IRC_AddClientMessage(irc, va("NOTICE %s :\001VERSION "FULLENGINENAME" "RELEASE" \001\r\n", prefix));
}
else if (!strncmp(col+1, "FTEICE ", 7))
{
IRC_Printf(irc, to, "ICE from %s\n", link); //from client
IRC_ICE_Parse(irc, to, col+8);
}
else
{
if (end)//put it back on. might as well.
*end = '\001';
IRC_Printf(irc, to, "%s: %s\n", link, col); //from client
}
}
else
{
IRC_FilterMircColours(col);
IRC_Printf(irc, to, "%s: %s\n", link, col); //from client
}
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "SERVER: <%s> %s\n", prefix, msg); //direct server message
}
else if (!strncmp(var[2], "MODE ", 5))
{
char *username = strtok(var[1]+1, "! ");
char *mode = strtok(var[4], " ");
char *target = strtok(var[5], " ");
char channel[100];
if (!strncmp(var[3], "#", 1))
{
strcpy(channel,strtok(var[3], " "));
}
else
{
strcpy(channel,DEFAULTCONSOLE);
}
if ((!strncmp(mode+1,"o", 1)) || (!strncmp(mode+1,"v",1))) // ops or voice
{
IRC_Printf(irc, channel,COLOURGREEN "%s sets mode %s on %s\n",username,mode,target);
}
else
{
if (IRC_WindowShown(irc, channel))
IRC_Printf(irc, channel, COLOURGREEN "%s sets mode %s\n",username,mode);
}
}
else if (!strncmp(var[2], "KICK ", 5))
{
char *username = strtok(var[1]+1, "!");
char *channel = strtok(var[3], " ");
char *target = strtok(var[4], " ");
char *reason = var[5]+1;
IRC_Printf(irc, channel,COLOURGREEN "%s was kicked from %s Reason: '%s' by %s\n",target,channel,reason,username);
}
else if (!strncmp(msg, "NICK ", 5))
{
char *exc = strchr(prefix, '!');
char *col = strchr(msg+5, ':');
if (exc && col)
{
*exc = '\0';
//fixme: print this in all channels as appropriate.
IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN "%s changes name to %s\n", prefix, col+1);
if (BUILTINISVALID(Con_RenameSub))
{
char oldname[256];
char newname[256];
Q_snprintf(oldname, sizeof(oldname), irc->id, prefix);
Q_snprintf(newname, sizeof(newname), irc->id, col+1);
pCon_RenameSub(oldname, newname); //if we were pming to them, rename accordingly.
}
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN ":%s%s\n", prefix, msg+6);
}
else if (!strncmp(msg, "QUIT ", 5))
{
/*char *exc = strchr(prefix, '!');
char *col = strchr(msg+5, ':');
if (exc && col)
{
*exc = '\0';
IRC_Printf(irc, col+1, COLOURGREEN "%s joins channel %s\n", prefix, col+1);
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN ":%s QUIT %s\n", prefix, msg+5);*/
}
else if (!strncmp(msg, "PART ", 5))
{
char *exc = strchr(prefix, '!');
COM_Parse(msg+5, token, sizeof(token));
if (exc)
{
*exc = '\0';
IRC_Printf(irc, token, "%s leaves channel %s\n", prefix, token);
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN ":%sPART %s\n", prefix, msg+5);
}
else if (!strncmp(msg, "JOIN ", 5))
{
char *exc = strchr(prefix, '!');
char *col = strchr(msg+5, ':');
if (exc && col)
{
*exc = '\0';
IRC_Printf(irc, col+1, COLOURGREEN "%s joins channel %s\n", prefix, col+1);
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN ":%s JOIN %s\n", prefix, msg+5);
}
else if (!strncmp(msg, "372 ", 4))
{
char *text = strstr(msg, ":-");
if (!*irc->autochannels || irc_motd.value)
{
if (text)
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", text+2);
else
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", msg);
}
}
else if (!strncmp(msg, "TOPIC ", 5))
{
char *topic = COM_Parse(msg+5, token, sizeof(token));
while (*topic == ' ')
topic++;
if (*topic++ == ':')
{
char *exc = strchr(prefix, '!');
if (exc)
*exc = 0;
IRC_Printf(irc, token, COLOURGREEN "%s changes topic to %s\n", prefix, topic);
}
else IRC_Printf(irc, DEFAULTCONSOLE, COLOURGREEN ":%s TOPIC %s\n", prefix, msg+5);
}
else if (!strncmp(msg, "331 ", 4) ||//no topic
!strncmp(msg, "332 ", 4)) //the topic
{
char *topic;
char *chan;
topic = COM_Parse(msg, token, sizeof(token));
topic = COM_Parse(topic, token, sizeof(token));
topic = COM_Parse(topic, token, sizeof(token));
while(*topic == ' ')
topic++;
if (*topic == ':')
{
topic++;
chan = token;
}
else
{
topic = "No topic";
chan = DEFAULTCONSOLE;
}
IRC_Printf(irc, chan, "Topic on channel %s is: "COLOURGREEN"%s\n", chan, topic);
}
else if (!strncmp(msg, "353 ", 4)) //the names of people on a channel
{
char *eq = strstr(msg, "="); // BAD SPIKE!! = is normal channel :(
char *eq2 = strstr(msg, "@"); // @ means the channel is +s (secret)
char *eq3 = strstr(msg, "*"); // * means the channel is +p (private) rather redundant...
char *channeltype = strtok(var[4], " ");
char *channel = strtok(var[5], " ");
char *str;
int secret = 0;
int privatechan = 0;
if ( !strcmp(channeltype,"=") )
{
char *end;
eq++;
str = strstr(eq, ":");
while(*eq == ' ')
eq++;
for (end = eq; *end>' '&&*end !=':'; end++)
;
*end = '\0';
str++;
}
//else if (eq2)
else if ( !strcmp(channeltype,"@") )
{
char *end;
secret = 1;
eq2++;
str = strstr(eq2, ":");
while(*eq2 == ' ')
eq2++;
for (end = eq2; *end>' '&&*end !=':'; end++)
;
*end = '\0';
str++;
}
else if ( !strcmp(channeltype,"*") )
{
char *end;
privatechan = 1;
eq3++;
str = strstr(eq3, ":");
while(*eq3 == ' ')
eq3++;
for (end = eq3; *end>' '&&*end !=':'; end++)
;
*end = '\0';
str++;
}
else
{
eq = "Corrupted_Message";
str = NULL;
}
IRC_Printf(irc, channel, va("Users on channel %s:\n", channel));
while (str)
{
str = COM_Parse(str, token, sizeof(token));
if (*token == '@') //they're an operator
IRC_Printf(irc, channel, "^[@"COLOURGREEN"%s\\act\\user\\who\\%s\\tip\\Channel Operator^]\n", token+1, token+1);
else if (*token == '%') //they've got half-op
IRC_Printf(irc, channel, "^[%%"COLOURGREEN"%s\\act\\user\\who\\%s\\tip\\Channel Half-Operator^]\n", token+1, token+1);
else if (*token == '+') //they've got voice
IRC_Printf(irc, channel, "^[+"COLOURGREEN"%s\\act\\user\\who\\%s\\tip\\Voice^]\n", token+1, token+1);
else
IRC_Printf(irc, channel, " ^["COLOURGREEN"%s\\act\\user\\who\\%s^]\n", token, token);
}
if (secret == 1)
{
IRC_Printf(irc, channel, "%s is secret (+s)\n",channel);
}
else if (privatechan == 1)
{
IRC_Printf(irc, channel, "%s is private (+p)\n",channel);
}
}
// would be great to convert the above to work better
else if (atoi(var[2]) != 0)
{
// char *rawparameter = strtok(var[4], " ");
// char *rawmessage = var[5];
// char *wholerawmessage = var[4];
numbered_command(atoi(var[2]), msg, irc);
IRC_CvarUpdate();
if (irc_debug.value == 1) { IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", msg); }
}
else
IRC_Printf(irc, DEFAULTCONSOLE, "%s\n", msg);
memmove(irc->bufferedinmessage, nextmsg, irc->bufferedinammount - (msg-irc->bufferedinmessage));
irc->bufferedinammount-=nextmsg-irc->bufferedinmessage;
return IRC_CONTINUE;
}
//functions above this line allow connections to multiple servers.
//it is just the control functions that only allow one server.
qintptr_t IRC_Frame(qintptr_t *args)
{
ircclient_t *ircclient;
if (reloadconfig)
{
reloadconfig = false;
IRC_ParseConfig();
}
for (ircclient = ircclients; ircclient; ircclient = ircclient->next)
{
int stat = IRC_CONTINUE;
if (!handleisvalid(ircclient->socket))
continue; //this connection isn't enabled.
while(stat == IRC_CONTINUE)
{
stat = IRC_ClientFrame(ircclient);
if (ircclient->bufferedoutammount)
{
int flushed = pNet_Send(ircclient->socket, ircclient->bufferedoutmessage, ircclient->bufferedoutammount); //FIXME: This needs rewriting to cope with errors+throttle.
if (flushed > 0)
{
memmove(ircclient->bufferedoutmessage, ircclient->bufferedoutmessage+flushed, ircclient->bufferedoutammount - flushed);
ircclient->bufferedoutammount -= flushed;
}
}
}
if (ircclient->quitting && !ircclient->bufferedoutammount)
stat = IRC_KILL;
if (stat == IRC_KILL)
{
pNet_Close(ircclient->socket);
ircclient->socket = invalid_handle;
IRC_Printf(ircclient, DEFAULTCONSOLE, "Disconnected from irc\n^[[Reconnect]\\act\\reconnect^]\n");
break; //lazy
}
else
IRC_ICE_Frame(ircclient);
}
return 0;
}
void IRC_Command(ircclient_t *ircclient, char *dest, char *args)
{
char token[1024];
char *msg;
msg = COM_Parse(args, token, sizeof(token));
if (*token == '/')
{
if (!strcmp(token+1, "server"))
{ //selects the default server without connecting anywhere, for main console to use.
msg = COM_Parse(msg, token, sizeof(token));
ircclient = IRC_FindAccount(token);
if (!ircclient)
IRC_Printf(ircclient, dest, "No such connection\n");
else if (ircclients == ircclient)
IRC_Printf(ircclient, dest, "Connection is already the default.\n");
else
{
IRC_MakeDefault(ircclient);
IRC_Printf(ircclient, dest, "Connection is now default.\n");
}
}
else if (!strcmp(token+1, "open") || !strcmp(token+1, "connect"))
{
char server[256];
char channels[1024];
char nick[256];
char password[256];
msg = COM_Parse(msg, server, sizeof(server));
msg = COM_Parse(msg, channels, sizeof(channels));
msg = COM_Parse(msg, nick, sizeof(nick));
msg = COM_Parse(msg, password, sizeof(password));
//set up some defaults
if (!*nick)
Q_strlcpy(nick, irc_nick.string, sizeof(nick));
if (!*nick)
pCvar_GetString("name", nick, sizeof(nick));
ircclient = IRC_FindAccount(server);
if (ircclient)
{
if (handleisvalid(ircclient->socket)) //don't need to do anything.
{
IRC_Printf(ircclient, dest, "IRC connection to %s already registered\n", server);
return; //silently ignore it if the account already exists
}
}
else
ircclient = IRC_Create(server, nick, irc_realname.string, irc_hostname.string, irc_username.string, password, channels);
if (ircclient)
{
IRC_MakeDefault(ircclient);
ircclient->persist |= !strcmp(token+1, "connect");
if (IRC_Establish(ircclient))
IRC_Printf(ircclient, dest, "Trying to connect\n");
else
IRC_Printf(ircclient, dest, "Unable to connect\n");
}
else
IRC_Printf(ircclient, dest, "Unable to open account\n");
IRC_WriteConfig();
}
else if (!strcmp(token+1, "help"))
{
IRC_Printf(ircclient, dest, "to connect to a server: /connect SERVER \"#chan #chan2,chan2password\" NICK SERVERPASSWORD\n");
IRC_Printf(ircclient, dest, "to disconnect from a server: /quit\n");
IRC_Printf(ircclient, dest, "to join a channel: /join\n");
IRC_Printf(ircclient, dest, "to leave a channel: /part\n");
IRC_Printf(ircclient, dest, "note that servers and channels will be remembered\n");
}
else if (!strcmp(token+1, "nick"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (!ircclient) //not yet connected.
pCvar_SetString(irc_nick.name, token);
else
{
if (!handleisvalid(ircclient->socket))
Q_strlcpy(ircclient->primarynick, token, sizeof(ircclient->primarynick));
ircclient->nicktries = 0;
IRC_SetNick(ircclient, token);
}
IRC_WriteConfig();
}
else if (!strcmp(token+1, "user"))
{
msg = COM_Parse(msg, token, sizeof(token));
pCvar_SetString(irc_username.name, token);
if (ircclient)
IRC_SetUser(ircclient, token);
IRC_WriteConfig();
}
else if (!strcmp(token+1, "info") || !strcmp(token+1, "status"))
{
ircclient_t *e;
struct ircice_s *ice;
for (e = ircclients; e; e = e->next)
{
IRC_Printf(ircclient, dest, "SERVER: ^[%s\\type\\irc /server \"%s\"^]\n", e->server, e->server);
if (e->connecting && handleisvalid(e->socket))
IRC_Printf(ircclient, dest, "<CONNECTING>\n");
else if (handleisvalid(e->socket))
IRC_Printf(ircclient, dest, "<CONNECTED>\n");
else
IRC_Printf(ircclient, dest, "<DISCONNECTED>\n");
if (e->quitting)
IRC_Printf(ircclient, dest, "<QUITTING>\n");
switch(e->tlsmode)
{
default:
case TLS_OFF:
IRC_Printf(ircclient, dest, "TLS: insecure\n");
break;
case TLS_INITIAL:
IRC_Printf(ircclient, dest, "TLS: initial\n");
break;
case TLS_START:
case TLS_STARTING:
IRC_Printf(ircclient, dest, "TLS: upgrade\n");
break;
}
IRC_Printf(ircclient, dest, "nick: %s\n", e->nick);
IRC_Printf(ircclient, dest, "realname: %s\n", e->realname);
IRC_Printf(ircclient, dest, "hostname: %s\n", e->hostname);
IRC_Printf(ircclient, dest, "rejoin: %s\n", *e->autochannels?e->autochannels:"<no channels>");
IRC_Printf(ircclient, dest, "default dest: %s\n", e->defaultdest);
for (ice = e->ice; ice; ice = ice->next)
{
char *allowed=ice->allowed?" allowed":" not-allowed";
char *accepted=ice->accepted?" accepted":" not-accepted";
switch(ice->type)
{
default:
case ICEP_INVALID:
IRC_Printf(ircclient, dest, " <INVALID ICE>\n");
break;
case ICEP_QWSERVER:
IRC_Printf(ircclient, dest, " server: \"%s\"%s%s\n", ice->peer, allowed, accepted);
break;
case ICEP_QWCLIENT:
IRC_Printf(ircclient, dest, " client: \"%s\"%s%s\n", ice->peer, allowed, accepted);
break;
case ICEP_VOICE:
IRC_Printf(ircclient, dest, " voice: \"%s\"%s%s\n", ice->peer, allowed, accepted);
break;
case ICEP_VIDEO:
IRC_Printf(ircclient, dest, " voice: \"%s\"%s%s\n", ice->peer, allowed, accepted);
break;
}
}
}
}
else if (!ircclient)
{
IRC_Printf(ircclient, dest, "Not connected, please connect to an irc server first.\n");
}
//ALL other commands require you to be connected.
else if (!strcmp(token+1, "list"))
{
IRC_AddClientMessage(ircclient, "LIST");
}
else if ( !strcmp(token+1, "join") || !strcmp(token+1, "j") )
{
char chan[256];
char pwd[256];
if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
{
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
return;
}
msg = COM_Parse(msg, chan, sizeof(chan));
msg = COM_Parse(msg, pwd, sizeof(pwd));
IRC_JoinChannel(ircclient,chan,pwd);
IRC_WriteConfig();
}
else if (!strcmp(token+1, "part") || !strcmp(token+1, "leave")) // need to implement leave reason
{
msg = COM_Parse(msg, token, sizeof(token));
IRC_PartChannel(ircclient, *token?token:dest);
IRC_WriteConfig();
}
else if (!strcmp(token+1, "call"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
{
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
return;
}
{
struct ircice_s *ice = IRC_ICE_Create(ircclient, *token?token:dest, ICEP_VOICE, true);
if (ice)
{
IRC_ICE_Update(ircclient, ice, '+');
IRC_Printf(ircclient, ice->peer, "<Calling %s>\n", ice->peer);
}
}
}
else if (!strcmp(token+1, "ginvite"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
{
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
return;
}
{
struct ircice_s *ice = IRC_ICE_Create(ircclient, *token?token:dest, ICEP_QWSERVER, true);
if (ice)
{
IRC_ICE_Update(ircclient, ice, '+');
IRC_Printf(ircclient, ice->peer, "<inviting %s>\n", ice->peer);
}
}
}
else if (!strcmp(token+1, "gatecrash"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
{
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
return;
}
{
struct ircice_s *ice = IRC_ICE_Create(ircclient, *token?token:dest, ICEP_QWCLIENT, true);
if (ice)
{
IRC_ICE_Update(ircclient, ice, '+');
IRC_Printf(ircclient, ice->peer, "<gatecrashing %s>\n", ice->peer);
}
}
}
else if (!strcmp(token+1, "msg"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (!msg)
return;
if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
{
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
return;
}
IRC_AddClientMessage(ircclient, va("PRIVMSG %s :%s", token, msg+1));
IRC_Printf(ircclient, token, "%s: %s\n", ircclient->nick, msg);
}
else if (!strcmp(token+1, "quote") || !strcmp(token+1, "raw"))
{
IRC_AddClientMessage(ircclient, va("%s", msg));
}
else if (!strcmp(token+1, "reconnect"))
{
if (IRC_Establish(ircclient))
IRC_Printf(ircclient, dest, "Trying to connect\n");
else
IRC_Printf(ircclient, dest, "Unable to connect\n");
}
else if (!strcmp(token+1, "quit") || !strcmp(token+1, "disconnect"))
{
msg = COM_Parse(msg, token, sizeof(token));
if (*token)
IRC_AddClientMessage(ircclient, va("QUIT :%s", token));
else
IRC_AddClientMessage(ircclient, va("QUIT :%s", irc_quitmessage.string));
ircclient->quitting = true;
IRC_WriteConfig();
}
else if (!strcmp(token+1, "whois"))
{
msg = COM_Parse(msg, token, sizeof(token));
IRC_AddClientMessage(ircclient, va("WHOIS :%s",token));
}
else if (!strcmp(token+1, "away"))
{
if ( strlen(msg) > 1 )
IRC_AddClientMessage(ircclient, va("AWAY :%s",msg+1));
else
IRC_AddClientMessage(ircclient, va("AWAY :"));
}
else if (!strcmp(token+1, "motd"))
{
IRC_AddClientMessage(ircclient, "MOTD");
}
else if (!strcmp(token+1, "ctcp"))
{
msg = COM_Parse(msg, token, sizeof(token));
IRC_AddClientMessage(ircclient, va("PRIVMSG %s :\1%s\1",token,msg+1));
}
else if (!strcmp(token+1, "dest"))
{
msg = COM_Parse(msg, token, sizeof(token));
Q_strlcpy(ircclient->defaultdest, token, sizeof(ircclient->defaultdest));
}
else if (!strcmp(token+1, "ping"))
{
if (!*dest)
IRC_Printf(ircclient, DEFAULTCONSOLE, "No channel joined. Try /join #<channel>\n");
else
IRC_AddClientMessage(ircclient, va("PRIVMSG %s :\001PING%s\001", dest, msg));
}
else if (!strcmp(token+1, "notice"))
{
msg = COM_Parse(msg, token, sizeof(token));
IRC_AddClientMessage(ircclient, va("NOTICE %s :%s",token, msg+1));
}
else if (!strcmp(token+1, "me"))
{
if (!*dest)
IRC_Printf(ircclient, DEFAULTCONSOLE, "No channel joined. Try /join #<channel>\n");
else
{
if(*msg <= ' ' && *msg)
msg++;
IRC_AddClientMessage(ircclient, va("PRIVMSG %s :\001ACTION %s\001", dest, msg));
IRC_Printf(ircclient, dest, "***^3%s^7 %s\n", ircclient->nick, msg);
}
}
else if (!strcmp(token+1, "topic"))
{
if (!*dest)
IRC_Printf(ircclient, DEFAULTCONSOLE, "No channel joined. Try /join #<channel>\n");
else
{
if(*msg <= ' ' && *msg)
msg++;
IRC_AddClientMessage(ircclient, va("TOPIC %s :%s", dest, msg));
}
}
else
IRC_Printf(ircclient, dest, "Command not recognised\n");
}
else
{
if (ircclient)
{
if (!handleisvalid(ircclient->socket))
IRC_Printf(ircclient, dest, "Connection was closed. use /reconnect\n");
else if (ircclient->tlsmode == TLS_STARTING || ircclient->connecting)
IRC_Printf(ircclient, dest, "Still connecting. Please wait.\n");
else if (!*dest)
{
IRC_Printf(ircclient, dest, "No channel joined. Try /join #<channel>\n");
}
else
{
msg = args;
while (*msg == ' ')
msg++;
if (!*msg)
return; //this is apparently an error. certainly wasteful.
IRC_AddClientMessage(ircclient, va("PRIVMSG %s :%s", dest, msg));
IRC_Printf(ircclient, dest, "^3%s^7: %s\n", ircclient->nick, msg);
}
}
else
IRC_Printf(ircclient, "Not connected\ntype \"%s /open IRCSERVER [#channel1[,#channel2[,...]]] [nick]\" to connect\n", COMMANDNAME);
}
}