/* X-Chat
* Copyright (C) 1998 Peter Zelezny.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include "hexchat.h"
#include "notify.h"
#include "cfgfiles.h"
#include "fe.h"
#include "server.h"
#include "text.h"
#include "util.h"
#include "hexchatc.h"
GSList *notify_list = 0;
int notify_tag = 0;
static char *
despacify_dup (char *str)
{
char *p, *res = g_malloc (strlen (str) + 1);
p = res;
while (1)
{
if (*str != ' ')
{
*p = *str;
if (*p == 0)
return res;
p++;
}
str++;
}
}
static int
notify_netcmp (char *str, void *serv)
{
char *net = despacify_dup (server_get_network (serv, TRUE));
if (rfc_casecmp (str, net) == 0)
{
g_free (net);
return 0; /* finish & return FALSE from token_foreach() */
}
g_free (net);
return 1; /* keep going... */
}
/* monitor this nick on this particular network? */
static gboolean
notify_do_network (struct notify *notify, server *serv)
{
if (!notify->networks) /* ALL networks for this nick */
return TRUE;
if (token_foreach (notify->networks, ',', notify_netcmp, serv))
return FALSE; /* network list doesn't contain this one */
return TRUE;
}
struct notify_per_server *
notify_find_server_entry (struct notify *notify, struct server *serv)
{
GSList *list = notify->server_list;
struct notify_per_server *servnot;
while (list)
{
servnot = (struct notify_per_server *) list->data;
if (servnot->server == serv)
return servnot;
list = list->next;
}
/* not found, should we add it, or is this not a network where
we're monitoring this nick? */
if (!notify_do_network (notify, serv))
return NULL;
servnot = g_new0 (struct notify_per_server, 1);
servnot->server = serv;
servnot->notify = notify;
notify->server_list = g_slist_prepend (notify->server_list, servnot);
return servnot;
}
void
notify_save (void)
{
int fh;
struct notify *notify;
// while reading the notify.conf file, elements are added by prepending to the
// list. reverse the list before writing to disk to keep the original
// order of the list
GSList *list = g_slist_copy(notify_list);
list = g_slist_reverse(list);
fh = hexchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
if (fh != -1)
{
while (list)
{
notify = (struct notify *) list->data;
write (fh, notify->name, strlen (notify->name));
if (notify->networks)
{
write (fh, " ", 1);
write (fh, notify->networks, strlen (notify->networks));
}
write (fh, "\n", 1);
list = list->next;
}
close (fh);
}
g_slist_free(list);
}
void
notify_load (void)
{
int fh;
char buf[256];
char *sep;
fh = hexchat_open_file ("notify.conf", O_RDONLY, 0, 0);
if (fh != -1)
{
while (waitline (fh, buf, sizeof buf, FALSE) != -1)
{
if (buf[0] != '#' && buf[0] != 0)
{
sep = strchr (buf, ' ');
if (sep)
{
sep[0] = 0;
notify_adduser (buf, sep + 1);
}
else
notify_adduser (buf, NULL);
}
}
close (fh);
}
}
static struct notify_per_server *
notify_find (server *serv, char *nick)
{
GSList *list = notify_list;
struct notify_per_server *servnot;
struct notify *notify;
while (list)
{
notify = (struct notify *) list->data;
servnot = notify_find_server_entry (notify, serv);
if (!servnot)
{
list = list->next;
continue;
}
if (!serv->p_cmp (notify->name, nick))
return servnot;
list = list->next;
}
return NULL;
}
static void
notify_announce_offline (server * serv, struct notify_per_server *servnot,
char *nick, int quiet,
const message_tags_data *tags_data)
{
session *sess;
sess = serv->front_session;
servnot->ison = FALSE;
servnot->lastoff = time (0);
if (!quiet)
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername,
server_get_network (serv, TRUE), NULL, 0,
tags_data->timestamp);
fe_notify_update (nick);
fe_notify_update (0);
}
static void
notify_announce_online (server * serv, struct notify_per_server *servnot,
char *nick, const message_tags_data *tags_data)
{
session *sess;
sess = serv->front_session;
servnot->lastseen = time (0);
if (servnot->ison)
return;
servnot->ison = TRUE;
servnot->laston = time (0);
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYONLINE, sess, nick, serv->servername,
server_get_network (serv, TRUE), NULL, 0,
tags_data->timestamp);
fe_notify_update (nick);
fe_notify_update (0);
if (prefs.hex_notify_whois_online)
{
/* Let's do whois with idle time (like in /quote WHOIS %s %s) */
char *wii_str = g_strdup_printf ("%s %s", nick, nick);
serv->p_whois (serv, wii_str);
g_free (wii_str);
}
}
/* handles numeric 601 */
void
notify_set_offline (server * serv, char *nick, int quiet,
const message_tags_data *tags_data)
{
struct notify_per_server *servnot;
servnot = notify_find (serv, nick);
if (!servnot)
return;
notify_announce_offline (serv, servnot, nick, quiet, tags_data);
}
/* handles numeric 604 and 600 */
void
notify_set_online (server * serv, char *nick,
const message_tags_data *tags_data)
{
struct notify_per_server *servnot;
servnot = notify_find (serv, nick);
if (!servnot)
return;
notify_announce_online (serv, servnot, nick, tags_data);
}
/* monitor can send lists for numeric 730/731 */
void
notify_set_offline_list (server * serv, char *users, int quiet,
const message_tags_data *tags_data)
{
struct notify_per_server *servnot;
char nick[NICKLEN];
char *token, *chr;
token = strtok (users, ",");
while (token != NULL)
{
chr = strchr (token, '!');
if (chr != NULL)
*chr = '\0';
g_strlcpy (nick, token, sizeof(nick));
servnot = notify_find (serv, nick);
if (servnot)
notify_announce_offline (serv, servnot, nick, quiet, tags_data);
token = strtok (NULL, ",");
}
}
void
notify_set_online_list (server * serv, char *users,
const message_tags_data *tags_data)
{
struct notify_per_server *servnot;
char nick[NICKLEN];
char *token, *chr;
token = strtok (users, ",");
while (token != NULL)
{
chr = strchr (token, '!');
if (chr != NULL)
*chr = '\0';
g_strlcpy (nick, token, sizeof(nick));
servnot = notify_find (serv, nick);
if (servnot)
notify_announce_online (serv, servnot, nick, tags_data);
token = strtok (NULL, ",");
}
}
static void
notify_watch (server * serv, char *nick, int add)
{
char tbuf[256];
char addchar = '+';
if (!add)
addchar = '-';
if (serv->supports_monitor)
g_snprintf (tbuf, sizeof (tbuf), "MONITOR %c %s", addchar, nick);
else if (serv->supports_watch)
g_snprintf (tbuf, sizeof (tbuf), "WATCH %c%s", addchar, nick);
else
return;
serv->p_raw (serv, tbuf);
}
static void
notify_watch_all (struct notify *notify, int add)
{
server *serv;
GSList *list = serv_list;
while (list)
{
serv = list->data;
if (serv->connected && serv->end_of_motd && notify_do_network (notify, serv))
notify_watch (serv, notify->name, add);
list = list->next;
}
}
static void
notify_flush_watches (server * serv, GSList *from, GSList *end)
{
char tbuf[512];
GSList *list;
struct notify *notify;
serv->supports_monitor ? strcpy (tbuf, "MONITOR + ") : strcpy (tbuf, "WATCH");
list = from;
while (list != end)
{
notify = list->data;
if (serv->supports_monitor)
g_strlcat (tbuf, ",", sizeof(tbuf));
else
g_strlcat (tbuf, " +", sizeof(tbuf));
g_strlcat (tbuf, notify->name, sizeof(tbuf));
list = list->next;
}
serv->p_raw (serv, tbuf);
}
/* called when logging in. e.g. when End of motd. */
void
notify_send_watches (server * serv)
{
struct notify *notify;
const int format_len = serv->supports_monitor ? 1 : 2; /* just , for monitor or + and space for watch */
GSList *list;
GSList *point;
GSList *send_list = NULL;
int len = 0;
/* Only get the list for this network */
list = notify_list;
while (list)
{
notify = list->data;
if (notify_do_network (notify, serv))
{
send_list = g_slist_append (send_list, notify);
}
list = list->next;
}
/* Now send that list in batches */
point = list = send_list;
while (list)
{
notify = list->data;
len += strlen (notify->name) + format_len;
if (len > 500)
{
/* Too long send existing list */
notify_flush_watches (serv, point, list);
len = strlen (notify->name) + format_len;
point = list; /* We left off here */
}
list = g_slist_next (list);
}
if (len) /* We had leftovers under 500, send them all */
{
notify_flush_watches (serv, point, NULL);
}
g_slist_free (send_list);
}
/* called when receiving a ISON 303 - should this func go? */
void
notify_markonline (server *serv, char *word[], const message_tags_data *tags_data)
{
struct notify *notify;
struct notify_per_server *servnot;
GSList *list = notify_list;
int i, seen;
while (list)
{
notify = (struct notify *) list->data;
servnot = notify_find_server_entry (notify, serv);
if (!servnot)
{
list = list->next;
continue;
}
i = 4;
seen = FALSE;
while (*word[i])
{
if (!serv->p_cmp (notify->name, word[i]))
{
seen = TRUE;
notify_announce_online (serv, servnot, notify->name, tags_data);
break;
}
i++;
/* FIXME: word[] is only a 32 element array, limits notify list to
about 27 people */
if (i > PDIWORDS - 5)
{
/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
break;
}
}
if (!seen && servnot->ison)
{
notify_announce_offline (serv, servnot, notify->name, FALSE, tags_data);
}
list = list->next;
}
fe_notify_update (0);
}
/* yuck! Old routine for ISON notify */
static void
notify_checklist_for_server (server *serv)
{
char outbuf[512];
struct notify *notify;
GSList *list = notify_list;
int i = 0;
strcpy (outbuf, "ISON ");
while (list)
{
notify = list->data;
if (notify_do_network (notify, serv))
{
i++;
strcat (outbuf, notify->name);
strcat (outbuf, " ");
if (strlen (outbuf) > 460)
{
/* LAME: we can't send more than 512 bytes to the server, but *
* if we split it in two packets, our offline detection wouldn't *
work */
/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
break;
}
}
list = list->next;
}
if (i)
serv->p_raw (serv, outbuf);
}
int
notify_checklist (void) /* check ISON list */
{
struct server *serv;
GSList *list = serv_list;
while (list)
{
serv = list->data;
if (serv->connected && serv->end_of_motd && !serv->supports_watch && !serv->supports_monitor)
{
notify_checklist_for_server (serv);
}
list = list->next;
}
return 1;
}
void
notify_showlist (struct session *sess, const message_tags_data *tags_data)
{
char outbuf[256];
struct notify *notify;
GSList *list = notify_list;
struct notify_per_server *servnot;
int i = 0;
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0,
tags_data->timestamp);
while (list)
{
i++;
notify = (struct notify *) list->data;
servnot = notify_find_server_entry (notify, sess->server);
if (servnot && servnot->ison)
g_snprintf (outbuf, sizeof (outbuf), _(" %-20s online\n"), notify->name);
else
g_snprintf (outbuf, sizeof (outbuf), _(" %-20s offline\n"), notify->name);
PrintTextTimeStamp (sess, outbuf, tags_data->timestamp);
list = list->next;
}
if (i)
{
sprintf (outbuf, "%d", i);
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL,
0, tags_data->timestamp);
} else
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0,
tags_data->timestamp);
}
int
notify_deluser (char *name)
{
struct notify *notify;
struct notify_per_server *servnot;
GSList *list = notify_list;
while (list)
{
notify = (struct notify *) list->data;
if (!rfc_casecmp (notify->name, name))
{
fe_notify_update (notify->name);
/* Remove the records for each server */
while (notify->server_list)
{
servnot = (struct notify_per_server *) notify->server_list->data;
notify->server_list =
g_slist_remove (notify->server_list, servnot);
g_free (servnot);
}
notify_list = g_slist_remove (notify_list, notify);
notify_watch_all (notify, FALSE);
g_free (notify->networks);
g_free (notify->name);
g_free (notify);
fe_notify_update (0);
return 1;
}
list = list->next;
}
return 0;
}
void
notify_adduser (char *name, char *networks)
{
struct notify *notify = g_new0 (struct notify, 1);
notify->name = g_strndup (name, NICKLEN - 1);
if (networks != NULL)
notify->networks = despacify_dup (networks);
notify->server_list = 0;
notify_list = g_slist_prepend (notify_list, notify);
notify_checklist ();
fe_notify_update (notify->name);
fe_notify_update (0);
notify_watch_all (notify, TRUE);
}
gboolean
notify_is_in_list (server *serv, char *name)
{
struct notify *notify;
GSList *list = notify_list;
while (list)
{
notify = (struct notify *) list->data;
if (!serv->p_cmp (notify->name, name))
return TRUE;
list = list->next;
}
return FALSE;
}
int
notify_isnotify (struct session *sess, char *name)
{
struct notify *notify;
struct notify_per_server *servnot;
GSList *list = notify_list;
while (list)
{
notify = (struct notify *) list->data;
if (!sess->server->p_cmp (notify->name, name))
{
servnot = notify_find_server_entry (notify, sess->server);
if (servnot && servnot->ison)
return TRUE;
}
list = list->next;
}
return FALSE;
}
void
notify_cleanup ()
{
GSList *list = notify_list;
GSList *nslist, *srvlist;
struct notify *notify;
struct notify_per_server *servnot;
struct server *serv;
int valid;
while (list)
{
/* Traverse the list of notify structures */
notify = (struct notify *) list->data;
nslist = notify->server_list;
while (nslist)
{
/* Look at each per-server structure */
servnot = (struct notify_per_server *) nslist->data;
/* Check the server is valid */
valid = FALSE;
srvlist = serv_list;
while (srvlist)
{
serv = (struct server *) srvlist->data;
if (servnot->server == serv)
{
valid = serv->connected; /* Only valid if server is too */
break;
}
srvlist = srvlist->next;
}
if (!valid)
{
notify->server_list =
g_slist_remove (notify->server_list, servnot);
g_free (servnot);
nslist = notify->server_list;
} else
{
nslist = nslist->next;
}
}
list = list->next;
}
fe_notify_update (0);
}