summaryrefslogtreecommitdiffstats
path: root/src/fe-common/irc/fe-netjoin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-common/irc/fe-netjoin.c')
-rw-r--r--src/fe-common/irc/fe-netjoin.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/src/fe-common/irc/fe-netjoin.c b/src/fe-common/irc/fe-netjoin.c
new file mode 100644
index 0000000..ea41f7f
--- /dev/null
+++ b/src/fe-common/irc/fe-netjoin.c
@@ -0,0 +1,515 @@
+/*
+ fe-netjoin.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/modes.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/irc/core/netsplit.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define NETJOIN_WAIT_TIME 5 /* how many seconds to wait for the netsplitted JOIN messages to stop */
+#define NETJOIN_MAX_WAIT 30 /* how many seconds to wait for nick to join to the rest of the channels she was before the netsplit */
+
+typedef struct {
+ char *nick;
+ GSList *old_channels;
+ GSList *now_channels;
+} NETJOIN_REC;
+
+typedef struct {
+ IRC_SERVER_REC *server;
+ time_t last_netjoin;
+
+ GSList *netjoins;
+} NETJOIN_SERVER_REC;
+
+typedef struct {
+ int count;
+ GString *nicks;
+} TEMP_PRINT_REC;
+
+static int join_tag;
+static int netjoin_max_nicks, hide_netsplit_quits;
+static int printing_joins;
+static GSList *joinservers;
+
+static NETJOIN_SERVER_REC *netjoin_find_server(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, NULL);
+
+ for (tmp = joinservers; tmp != NULL; tmp = tmp->next) {
+ NETJOIN_SERVER_REC *rec = tmp->data;
+
+ if (rec->server == server)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static NETJOIN_REC *netjoin_add(IRC_SERVER_REC *server, const char *nick,
+ GSList *channels)
+{
+ NETJOIN_REC *rec;
+ NETJOIN_SERVER_REC *srec;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new0(NETJOIN_REC, 1);
+ rec->nick = g_strdup(nick);
+ while (channels != NULL) {
+ NETSPLIT_CHAN_REC *channel = channels->data;
+
+ rec->old_channels = g_slist_append(rec->old_channels,
+ g_strdup(channel->name));
+ channels = channels->next;
+ }
+
+ srec = netjoin_find_server(server);
+ if (srec == NULL) {
+ srec = g_new0(NETJOIN_SERVER_REC, 1);
+ srec->server = server;
+ joinservers = g_slist_append(joinservers, srec);
+ }
+
+ srec->last_netjoin = time(NULL);
+ srec->netjoins = g_slist_append(srec->netjoins, rec);
+ return rec;
+}
+
+static NETJOIN_REC *netjoin_find(IRC_SERVER_REC *server, const char *nick)
+{
+ NETJOIN_SERVER_REC *srec;
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ srec = netjoin_find_server(server);
+ if (srec == NULL) return NULL;
+
+ for (tmp = srec->netjoins; tmp != NULL; tmp = tmp->next) {
+ NETJOIN_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->nick, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void netjoin_remove(NETJOIN_SERVER_REC *server, NETJOIN_REC *rec)
+{
+ server->netjoins = g_slist_remove(server->netjoins, rec);
+
+ g_slist_foreach(rec->old_channels, (GFunc) g_free, NULL);
+ g_slist_foreach(rec->now_channels, (GFunc) g_free, NULL);
+ g_slist_free(rec->old_channels);
+ g_slist_free(rec->now_channels);
+
+ g_free(rec->nick);
+ g_free(rec);
+}
+
+static void netjoin_server_remove(NETJOIN_SERVER_REC *server)
+{
+ joinservers = g_slist_remove(joinservers, server);
+
+ while (server->netjoins != NULL)
+ netjoin_remove(server, server->netjoins->data);
+ g_free(server);
+}
+
+static void print_channel_netjoins(char *channel, TEMP_PRINT_REC *rec,
+ NETJOIN_SERVER_REC *server)
+{
+ if (rec->nicks->len > 0)
+ g_string_truncate(rec->nicks, rec->nicks->len-2);
+
+ printformat(server->server, channel, MSGLEVEL_JOINS,
+ rec->count > netjoin_max_nicks ?
+ IRCTXT_NETSPLIT_JOIN_MORE : IRCTXT_NETSPLIT_JOIN,
+ rec->nicks->str, rec->count-netjoin_max_nicks);
+
+ g_string_free(rec->nicks, TRUE);
+ g_free(rec);
+ g_free(channel);
+}
+
+static void print_netjoins(NETJOIN_SERVER_REC *server, const char *filter_channel)
+{
+ TEMP_PRINT_REC *temp;
+ GHashTable *channels;
+ GSList *tmp, *tmp2, *next, *next2, *old;
+
+ g_return_if_fail(server != NULL);
+
+ printing_joins = TRUE;
+
+ /* save nicks to string, clear now_channels and remove the same
+ channels from old_channels list */
+ channels = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+ for (tmp = server->netjoins; tmp != NULL; tmp = next) {
+ NETJOIN_REC *rec = tmp->data;
+
+ next = g_slist_next(tmp);
+
+ for (tmp2 = rec->now_channels; tmp2 != NULL; tmp2 = next2) {
+ char *channel = tmp2->data;
+ char *realchannel = channel + 1;
+
+ next2 = g_slist_next(tmp2);
+
+ /* Filter the results by channel if asked to do so */
+ if (filter_channel != NULL &&
+ strcasecmp(realchannel, filter_channel) != 0)
+ continue;
+
+ temp = g_hash_table_lookup(channels, realchannel);
+ if (temp == NULL) {
+ temp = g_new0(TEMP_PRINT_REC, 1);
+ temp->nicks = g_string_new(NULL);
+ g_hash_table_insert(channels,
+ g_strdup(realchannel),
+ temp);
+ }
+
+ temp->count++;
+ if (temp->count <= netjoin_max_nicks) {
+ if (*channel != ' ')
+ g_string_append_c(temp->nicks,
+ *channel);
+ g_string_append_printf(temp->nicks, "%s, ",
+ rec->nick);
+ }
+
+ /* remove the channel from old_channels too */
+ old = i_slist_find_icase_string(rec->old_channels, realchannel);
+ if (old != NULL) {
+ void *data = old->data;
+ rec->old_channels =
+ g_slist_remove(rec->old_channels, data);
+ g_free(data);
+ }
+
+ /* drop tmp2 from the list */
+ rec->now_channels = g_slist_delete_link(rec->now_channels, tmp2);
+ g_free(channel);
+ }
+
+ if (rec->old_channels == NULL)
+ netjoin_remove(server, rec);
+ }
+
+ g_hash_table_foreach(channels, (GHFunc) print_channel_netjoins,
+ server);
+ g_hash_table_destroy(channels);
+
+ if (server->netjoins == NULL)
+ netjoin_server_remove(server);
+
+ printing_joins = FALSE;
+}
+
+/* something is going to be printed to screen, print our current netsplit
+ message before it. */
+static void sig_print_starting(TEXT_DEST_REC *dest)
+{
+ NETJOIN_SERVER_REC *rec;
+
+ if (printing_joins)
+ return;
+
+ if (!IS_IRC_SERVER(dest->server))
+ return;
+
+ rec = netjoin_find_server(IRC_SERVER(dest->server));
+ if (rec != NULL && rec->netjoins != NULL) {
+ /* if netjoins exists, the server rec should be
+ still valid. otherwise, calling server->ischannel
+ may not be safe. */
+ if (dest->target != NULL &&
+ !server_ischannel((SERVER_REC *) rec->server, dest->target))
+ return;
+
+ print_netjoins(rec, NULL);
+ }
+}
+
+static int sig_check_netjoins(void)
+{
+ GSList *tmp, *next;
+ int diff;
+ time_t now;
+
+ now = time(NULL);
+ /* first print all netjoins which haven't had any new joins
+ * for NETJOIN_WAIT_TIME; this may cause them to be removed
+ * (all users who rejoined, rejoined all channels) */
+ for (tmp = joinservers; tmp != NULL; tmp = next) {
+ NETJOIN_SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ diff = now-server->last_netjoin;
+ if (diff <= NETJOIN_WAIT_TIME) {
+ /* wait for more JOINs */
+ continue;
+ }
+
+ if (server->netjoins != NULL)
+ print_netjoins(server, NULL);
+ }
+
+ /* now remove all netjoins which haven't had any new joins
+ * for NETJOIN_MAX_WAIT (user rejoined some but not all channels
+ * after split) */
+ for (tmp = joinservers; tmp != NULL; tmp = next) {
+ NETJOIN_SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ diff = now-server->last_netjoin;
+ if (diff >= NETJOIN_MAX_WAIT) {
+ /* waited long enough, forget about the rest */
+ netjoin_server_remove(server);
+ }
+ }
+
+ if (joinservers == NULL) {
+ g_source_remove(join_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ join_tag = -1;
+ }
+ return 1;
+}
+
+static void msg_quit(IRC_SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ if (IS_IRC_SERVER(server) && quitmsg_is_split(reason))
+ signal_stop();
+}
+
+static void msg_join(IRC_SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ NETSPLIT_REC *split;
+ NETJOIN_REC *netjoin;
+ GSList *channels;
+ int rejoin = 1;
+
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ if (ignore_check(SERVER(server), nick, address,
+ channel, NULL, MSGLEVEL_JOINS))
+ return;
+
+ split = netsplit_find(server, nick, address);
+ netjoin = netjoin_find(server, nick);
+ if (split == NULL && netjoin == NULL)
+ return;
+
+ /* if this was not a channel they split from, treat it normally */
+ if (netjoin != NULL) {
+ if (!i_slist_find_icase_string(netjoin->old_channels, channel))
+ return;
+ } else {
+ channels = split->channels;
+ while (channels != NULL) {
+ NETSPLIT_CHAN_REC *schannel = channels->data;
+
+ if (!strcasecmp(schannel->name, channel))
+ break;
+ channels = channels->next;
+ }
+ /* we still need to create a NETJOIN_REC now as the
+ * NETSPLIT_REC will be destroyed */
+ if (channels == NULL)
+ rejoin = 0;
+ }
+
+ if (join_tag == -1) {
+ join_tag = g_timeout_add(1000, (GSourceFunc)
+ sig_check_netjoins, NULL);
+ signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+
+ if (netjoin == NULL)
+ netjoin = netjoin_add(server, nick, split->channels);
+
+ if (rejoin)
+ {
+ netjoin->now_channels = g_slist_append(netjoin->now_channels,
+ g_strconcat(" ", channel, NULL));
+ signal_stop();
+ }
+}
+
+static int netjoin_set_nickmode(IRC_SERVER_REC *server, NETJOIN_REC *rec,
+ const char *channel, char prefix)
+{
+ GSList *pos;
+ const char *flags;
+ char *found_chan = NULL;
+
+ for (pos = rec->now_channels; pos != NULL; pos = pos->next) {
+ char *chan = pos->data;
+ if (strcasecmp(chan+1, channel) == 0) {
+ found_chan = chan;
+ break;
+ }
+ }
+
+ if (found_chan == NULL)
+ return FALSE;
+
+ flags = server->get_nick_flags(SERVER(server));
+ while (*flags != '\0') {
+ if (found_chan[0] == *flags)
+ break;
+ if (prefix == *flags) {
+ found_chan[0] = prefix;
+ break;
+ }
+ flags++;
+ }
+ return TRUE;
+}
+
+static void msg_mode(IRC_SERVER_REC *server, const char *channel,
+ const char *sender, const char *addr, const char *data)
+{
+ NETJOIN_REC *rec;
+ char *params, *mode, *nicks;
+ char **nicklist, **nick, type, prefix;
+ int show;
+
+ g_return_if_fail(data != NULL);
+ if (!server_ischannel(SERVER(server), channel) || addr != NULL)
+ return;
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &mode, &nicks);
+
+ /* parse server mode changes - hide operator status changes and
+ show them in the netjoin message instead as @ before the nick */
+ nick = nicklist = g_strsplit(nicks, " ", -1);
+
+ type = '+'; show = FALSE;
+ for (; *mode != '\0'; mode++) {
+ if (*mode == '+' || *mode == '-') {
+ type = *mode;
+ continue;
+ }
+
+ if (*nick != NULL && GET_MODE_PREFIX(server, *mode)) {
+ /* give/remove ops */
+ rec = netjoin_find(server, *nick);
+ prefix = GET_MODE_PREFIX(server, *mode);
+ if (rec == NULL || type != '+' || prefix == '\0' ||
+ !netjoin_set_nickmode(server, rec, channel, prefix))
+ show = TRUE;
+ nick++;
+ } else {
+ if (HAS_MODE_ARG(server, type, *mode) && *nick != NULL)
+ nick++;
+ show = TRUE;
+ }
+ }
+
+ if (!show) signal_stop();
+
+ g_strfreev(nicklist);
+ g_free(params);
+}
+
+static void read_settings(void)
+{
+ int old_hide;
+
+ old_hide = hide_netsplit_quits;
+ hide_netsplit_quits = settings_get_bool("hide_netsplit_quits");
+ netjoin_max_nicks = settings_get_int("netjoin_max_nicks");
+
+ if (old_hide && !hide_netsplit_quits) {
+ signal_remove("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_remove("message join", (SIGNAL_FUNC) msg_join);
+ signal_remove("message irc mode", (SIGNAL_FUNC) msg_mode);
+ } else if (!old_hide && hide_netsplit_quits) {
+ signal_add("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_add("message join", (SIGNAL_FUNC) msg_join);
+ signal_add("message irc mode", (SIGNAL_FUNC) msg_mode);
+ }
+}
+
+static void sig_server_disconnected(IRC_SERVER_REC *server)
+{
+ NETJOIN_SERVER_REC *netjoin_server;
+
+ g_return_if_fail(server != NULL);
+
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ if ((netjoin_server = netjoin_find_server(server))) {
+ netjoin_server_remove(netjoin_server);
+ }
+}
+
+void fe_netjoin_init(void)
+{
+ settings_add_bool("misc", "hide_netsplit_quits", TRUE);
+ settings_add_int("misc", "netjoin_max_nicks", 10);
+
+ join_tag = -1;
+ printing_joins = FALSE;
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+}
+
+void fe_netjoin_deinit(void)
+{
+ while (joinservers != NULL)
+ netjoin_server_remove(joinservers->data);
+ if (join_tag != -1) {
+ g_source_remove(join_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+
+ signal_remove("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_remove("message join", (SIGNAL_FUNC) msg_join);
+ signal_remove("message irc mode", (SIGNAL_FUNC) msg_mode);
+}