811bce25f1
Change revision displays, use the SVN commit date instead of using __DATE__ (when there's no local changes). This should allow reproducible builds. Added s_al_disable cvar, to block openal and all the various problems people have had with it, without having to name an explicit fallback (which would vary by system). Add mastervolume cvar (for ss). Add r_shadows 2 (aka fake shadows - for ss). Add scr_loadingscreen_aspect -1 setting, to disable levelshots entirely, also disables the progress bar (for ss). Better support for some effectinfo hacks (for ss). Added dpcompat_nocsqcwarnings (because of lazy+buggy mods like ss). Rework the dpcsqc versions of project+unproject builtins for better compat (for ss). Added dpcompat_csqcinputeventtypes to block unexpected csqc input events (for ss). Better compat with DP's loadfont console command (for ss). Added dpcompat_smallerfonts cvar to replicate a DP bug (for ss). Detect dp's m_draw extension, to work around it (for ss). Cvar dpcompat_ignoremodificationtimes added. A value of 0 favour the most recently modified file, 1 will use DP-like alphabetically sorted preferences (for ss). loadfont builtin can now accept outline=1 in the sizes arg for slightly more readable fonts. Fix bbox calcs for rotated entities, fix needed for r_ignorenetpvs 0. Hackily parse emoji.json to provide 💩 etc suggestions. Skip prediction entirely when there's no local entity info. This fixes stair-smoothing in xonotic. screenshot_cubemap will now capture half-float images when saving to ktx or dds files. Fix support for xcf files larger than 4gb, mostly to avoid compiler warnings. Fixed size of gfx/loading.lmp when replacement textures are used. Added mipmap support for rg8 and l8a8 textures. r_hdr_framebuffer cvar updated to support format names instead of random negative numbers. Description updated to name some interesting ones. Perform autoupdate _checks_ ONLY with explicit user confirmation (actual updating already needed user confirmation, but this extra step should reduce the chances of us getting wrongly accused of exfiltrating user data if we're run in a sandbox - we ONLY ever included the updating engine's version in the checks, though there's nothing we can do to avoid sending the user's router's IP). Removed the 'summon satan all over your harddrive' quit message, in case paranoid security researchers are idiots and don't bother doing actual research. Removed the triptohell.info and fte.triptohell.info certificates, they really need to stop being self-signed. The updates domain is still self-signed for autoupdates. Video drivers are now able to report supported video resolutions, visible to menuqc. Currently only works with SDL2 builds. Added setmousepos builtin. Should work with glx+win32 build. VF_SKYROOM_CAMERA can now accept an extra two args, setviewprop(VF_SKYROOM_CAMERA, org, axis, degrees). Removed v_skyroom_origin+v_skyroom_orientation cvars in favour just v_skyroom, which should make it behave more like the 'fog' command (used when csqc isn't overriding). Added R_EndPolygonRibbon builtin to make it faster+easier to generate textured ribbon/cable/etc wide lines (for TW). sdl: Fix up sys_sdl.c's file enumeration to support wildcards in directories. edit command now displays end1.bin/end2.bin correctly, because we can. Finally add support for f_modified - though ruleset_allow_larger_models and ruleset_allow_overlong_sounds generally make it redundant. Fix threading race condition in sha1 lookups. Updated f_ruleset to include the same extra flags reported by ezquake. A mod's default.fmf file can now contain an eg 'mainconfig config.cfg' line (to explicitly set the main config saved with cfg_save_auto 1 etc). fmf: basegame steam:GameName/GameDir can be used to try to load a mod directory from an installed steam game. The resulting gamedir will be read-only. HOMEDIR CHANGE: use homedirs only if the basedir cannot be written or a homedir already exists, which should further reduce the probability of microsoft randomly uploading our data to their cloud (but mostly because its annoying to never know where your data is written). Fixed buf_cvarlist, should work in xonotic now, and without segfaults. Added an extra arg to URI_Get_Callback calls - the response size, also changed the tempstring to contain all bytes of the response, you need to be careful about nulls though. Try to work around nvidia's forced-panning bug on x11 when changing video modes. This might screw with other programs. sdl: support custom icons. sdl: support choosing a specific display. Added some documentation to menuqc builtins. menusys: use outlines for slightly more readable fonts. menusys: switch vid_width and vid_height combos into a single video mode combo to set both according to reported video modes. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5581 fc73d0e0-1445-4013-8a0c-d673dee63da5
2816 lines
74 KiB
C
2816 lines
74 KiB
C
//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"
|
|
static plugsubconsolefuncs_t *confuncs;
|
|
static plugfsfuncs_t *filefuncs;
|
|
static plugnetfuncs_t *netfuncs;
|
|
static plug2dfuncs_t *drawfuncs;
|
|
#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"
|
|
|
|
#if defined(SVNREVISION)
|
|
#define RELEASE STRINGIFY(SVNREVISION)
|
|
#else
|
|
#define RELEASE __DATE__
|
|
#endif
|
|
|
|
static struct
|
|
{
|
|
int width;
|
|
int height;
|
|
} pvid;
|
|
static void QDECL IRC_UpdateVideo(int width, int height)
|
|
{
|
|
pvid.width = width;
|
|
pvid.height = height;
|
|
}
|
|
|
|
static qboolean (*Con_TrySubPrint)(const char *subname, const char *text);
|
|
static qboolean Con_FakeSubPrint(const char *subname, const char *text)
|
|
{
|
|
plugfuncs->Print(text);
|
|
return true;
|
|
}
|
|
|
|
//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 (confuncs && confuncs->GetConsoleFloat(lwr, "iswindow") < true)
|
|
{
|
|
confuncs->SetConsoleString(lwr, "title", *channame?channame:irc->server);
|
|
confuncs->SetConsoleString(lwr, "prompt", va("[^1%s^7]: ", irc->nick));
|
|
confuncs->SetConsoleFloat(lwr, "iswindow", 2);
|
|
confuncs->SetConsoleFloat(lwr, "forceutf8", true);
|
|
confuncs->SetConsoleFloat(lwr, "wnd_w", 256);
|
|
confuncs->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;
|
|
}
|
|
confuncs->SetConsoleFloat(lwr, "wnd_x", next_window_x);
|
|
confuncs->SetConsoleFloat(lwr, "wnd_y", next_window_y);
|
|
next_window_x += 256;
|
|
}
|
|
|
|
if (confuncs)
|
|
confuncs->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 (confuncs && confuncs->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)
|
|
plugfuncs->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 (confuncs && confuncs->GetConsoleFloat(lwr, "iswindow") < true)
|
|
{
|
|
confuncs->SetConsoleString(lwr, "title", *channame?channame:irc->server);
|
|
confuncs->SetConsoleString(lwr, "prompt", va("[^1%s^7]: ", irc->nick));
|
|
confuncs->SetConsoleFloat(lwr, "iswindow", 2);
|
|
confuncs->SetConsoleFloat(lwr, "forceutf8", true);
|
|
confuncs->SetConsoleFloat(lwr, "wnd_w", 256);
|
|
confuncs->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;
|
|
}
|
|
confuncs->SetConsoleFloat(lwr, "wnd_x", next_window_x);
|
|
confuncs->SetConsoleFloat(lwr, "wnd_y", next_window_y);
|
|
next_window_x += 256;
|
|
}
|
|
if (!*string)
|
|
confuncs->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 = cvarfuncs->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];
|
|
cvarfuncs->Update(v->handle, &v->modificationcount, v->string, sizeof(v->string), &v->value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void IRC_Command(ircclient_t *ircclient, char *dest, char *args);
|
|
qboolean IRC_ExecuteCommand(qboolean isinsecure);
|
|
int IRC_ConExecuteCommand(qboolean isinsecure);
|
|
void IRC_Frame(double realtime, double gametime);
|
|
qboolean IRC_ConsoleLink(void);
|
|
|
|
qboolean Plug_Init(void)
|
|
{
|
|
confuncs = plugfuncs->GetEngineInterface(plugsubconsolefuncs_name, sizeof(*confuncs));
|
|
filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs));
|
|
netfuncs = plugfuncs->GetEngineInterface(plugnetfuncs_name, sizeof(*netfuncs));
|
|
drawfuncs = plugfuncs->GetEngineInterface(plug2dfuncs_name, sizeof(*drawfuncs));
|
|
piceapi = plugfuncs->GetEngineInterface(ICE_API_CURRENT, sizeof(*piceapi));
|
|
plugfuncs->ExportFunction("UpdateVideo", IRC_UpdateVideo);
|
|
|
|
if (netfuncs &&
|
|
filefuncs &&
|
|
plugfuncs->ExportFunction("Tick", IRC_Frame) &&
|
|
plugfuncs->ExportFunction("ExecuteCommand", IRC_ExecuteCommand))
|
|
{
|
|
cmdfuncs->AddCommand(COMMANDNAME);
|
|
|
|
plugfuncs->ExportFunction("ConsoleLink", IRC_ConsoleLink);
|
|
if (!confuncs || !plugfuncs->ExportFunction("ConExecuteCommand", IRC_ConExecuteCommand))
|
|
Con_TrySubPrint = Con_FakeSubPrint;
|
|
else
|
|
Con_TrySubPrint = confuncs->SubPrint;
|
|
|
|
reloadconfig = true;
|
|
IRC_InitCvars();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
plugfuncs->Print("IRC Client Plugin failed\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
qboolean IRC_ExecuteCommand(qboolean isinsecure)
|
|
{
|
|
char cmd[256];
|
|
cmdfuncs->Argv(0, cmd, sizeof(cmd));
|
|
if (!strcmp(cmd, COMMANDNAME))
|
|
{
|
|
ircclient_t *ircclient = ircclients;
|
|
char imsg[8192];
|
|
cmdfuncs->Args(imsg, sizeof(imsg));
|
|
//FIXME: select an irc network more inteligently
|
|
IRC_Command(ircclient, ircclient?ircclient->defaultdest:"", imsg);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
int IRC_ConExecuteCommand(qboolean isinsecure)
|
|
{
|
|
char buffer[256];
|
|
char imsg[8192];
|
|
ircclient_t *ircclient;
|
|
//FIXME: select the right network
|
|
|
|
cmdfuncs->Argv(0, buffer, sizeof(buffer));
|
|
cmdfuncs->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 = netfuncs->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 (netfuncs->SetTLSClient(irc->socket, irc->server) < 0)
|
|
{
|
|
netfuncs->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 = filefuncs->Open("**plugconfig", &config, 1);
|
|
if (len >= 0)
|
|
{
|
|
char *buf = malloc(len+1);
|
|
char *msg = buf;
|
|
buf[len] = 0;
|
|
filefuncs->Read(config, buf, len);
|
|
filefuncs->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;
|
|
|
|
filefuncs->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;
|
|
filefuncs->Write(config, s, strlen(s));
|
|
}
|
|
|
|
filefuncs->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 */
|
|
{
|
|
netfuncs->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]);
|
|
netfuncs->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");
|
|
}
|
|
|
|
qboolean IRC_ConsoleLink(void)
|
|
{
|
|
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;
|
|
// cmdfuncs->Argv(0, text, sizeof(text));
|
|
cmdfuncs->Argv(1, link, sizeof(link));
|
|
cmdfuncs->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 = netfuncs->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 (drawfuncs)
|
|
drawfuncs->LocalSound ("misc/talk.wav", 256, 1);
|
|
|
|
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
|
|
if (Q_snprintf(link, sizeof(link), "^["COLOURGREEN"%s\\act\\user\\who\\%s^]", prefix, prefix) >= sizeof(link))
|
|
Q_snprintf(link, sizeof(link), "%s", 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 (confuncs)
|
|
{
|
|
char oldname[256];
|
|
char newname[256];
|
|
Q_snprintf(oldname, sizeof(oldname), irc->id, prefix);
|
|
Q_snprintf(newname, sizeof(newname), irc->id, col+1);
|
|
confuncs->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.
|
|
|
|
void IRC_Frame(double realtime, double gametime)
|
|
{
|
|
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 = netfuncs->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)
|
|
{
|
|
netfuncs->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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
cvarfuncs->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.
|
|
cvarfuncs->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));
|
|
cvarfuncs->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);
|
|
}
|
|
}
|