added heavy user-list replay system
authorpk910 <philipp@zoelle1.de>
Thu, 10 May 2012 23:15:06 +0000 (01:15 +0200)
committerpk910 <philipp@zoelle1.de>
Thu, 10 May 2012 23:34:05 +0000 (01:34 +0200)
src/IRCClient.c
src/IRCClient.h

index eee86b1f8162d78e6f4fc81cc0416dfed58b17ef..7720602acac787f1658762dea9b2b116cb702017 100644 (file)
@@ -59,6 +59,14 @@ void ircclient_close(struct IRCClient *client) {
         free(recover_line->line);
         free(recover_line);
     }
+    if(client->network_prefixes)
+        free(client->network_prefixes);
+    if(client->network_prefixes_char)
+        free(client->network_prefixes_char);
+    if(client->network_chanmodes)
+        free(client->network_chanmodes);
+    if(client->network_chantypes)
+        free(client->network_chantypes);
     free(client);
 }
 
@@ -101,6 +109,160 @@ static struct IRCUser ircclient_parse_user(char *from) {
     return user;
 }
 
+static void ircclient_userlist_clear(struct IRCChannel *channel) {
+    struct IRCChannelMember *member, *next_member;
+    for(member = channel->userlist; member; member = next_member) {
+        next_member = member->next;
+        free(member->nick);
+        free(member);
+    }
+    channel->userlist = NULL;
+}
+
+static struct IRCChannelMember *ircclient_userlist_add(struct IRCChannel *channel, char *nick) {
+    struct IRCChannelMember *member;
+    for(member = channel->userlist; member; member = member->next) {
+        if(!stricmp(member->nick, nick)) return member; //prevent duplicates
+    }
+    member = calloc(1, sizeof(*member));
+    member->nick = strdup(nick);
+    member->next = channel->userlist;
+    if(channel->userlist)
+        channel->userlist->prev = member;
+    channel->userlist = member;
+    return member;
+}
+
+static void ircclient_userlist_del(struct IRCChannel *channel, char *nick) {
+    struct IRCChannelMember *member;
+    for(member = channel->userlist; member; member = member->next) {
+        if(!stricmp(member->nick, nick)) break;
+    }
+    if(!member) return;
+    if(member->prev)
+        member->prev->next = member->next;
+    else
+        channel->userlist = member->next;
+    if(member->next)
+        member->next->prev = member->prev;
+    free(member->nick);
+    free(member);
+}
+
+static void ircclient_raw005_parse(struct IRCClient *client, char **argv, int argc) {
+    int i;
+    char *name;
+    char *value;
+    for(i = 0; i < argc; i++) {
+        name = argv[i];
+        value = strchr(argv[i], '=');
+        if(value) {
+            *value = '\0';
+            value++;
+        }
+        if(!stricmp(name, "PREFIX")) {
+            //parse prefixes
+            if(*value != '(') continue;
+            value++;
+            char *prefixes_char = value;
+            value = strchr(value, ')');
+            if(!value) continue;
+            *value = '\0';
+            value++;
+            if(strlen(prefixes_char) == strlen(value)) {
+                client->network_prefixes = strdup(value);
+                client->network_prefixes_char = strdup(prefixes_char);
+            }
+        }
+    }
+}
+
+static void ircclient_chanmode_parse(struct IRCClient *client, struct IRCChannel *channel, char *mode, char **argv, int argc) {
+    int add = 1;
+    int arg = 0;
+    int i, j;
+    char *carg;
+    struct IRCChannelMember *member;
+    while(*mode) {
+        if(*mode == '+')
+            add = 1;
+        else if(*mode == '-')
+            add = 0;
+        else {
+            char *prefix_chars = client->network_prefixes_char ? client->network_prefixes_char : "ov";
+            i = 0;
+            while(prefix_chars[i]) {
+                if(prefix_chars[i] == *mode) {
+                    carg = argv[arg++];
+                    member = ircclient_userlist_add(channel, carg);
+                    if(add)
+                        member->modes |= (1 << i);
+                    else
+                        member->modes &= ~(1 << i);
+                    goto ircclient_chanmode_parse_next;
+                }
+                i++;
+            }
+            char *chanmodes = client->network_chanmodes ? client->network_chanmodes : "b,k,l,";
+            i = 0;
+            j = 0;
+            while(chanmodes[i]) {
+                if(chanmodes[i] == ',') {
+                    j++;
+                    if(j == 2 && !add) break;
+                    if(j == 3) break;
+                }
+                if(chanmodes[i] == *mode) {
+                    arg++;
+                    goto ircclient_chanmode_parse_next;
+                }
+            }
+            //default mode without parameter...
+        }
+        ircclient_chanmode_parse_next:
+        mode++;
+    }
+}
+
+static int ircclient_is_channel_prefix(struct IRCClient *client, char prefix) {
+    if(!client->network_chantypes) {
+        return (prefix == '#'); //default
+    }
+    int i = 0;
+    while(client->network_chantypes[i]) {
+        if(prefix == client->network_chantypes[i])
+            return 1;
+        i++;
+    }
+    return 0;
+}
+
+static int ircclient_is_chanuser_prefix(struct IRCClient *client, char prefix, int *prefix_id) {
+    char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
+    int i;
+    for(i = 0; prefixes[i]; i++) {
+        if(prefixes[i] == prefix) {
+            *prefix_id = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int ircclient_build_chanuser_prefix(struct IRCClient *client, struct IRCChannelMember *member, char *buffer, int highest_only) {
+    char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
+    int i;
+    int j = 0;
+    for(i = 0; prefixes[i]; i++) {
+        if(member->modes & (1 << i)) {
+            buffer[j++] = prefixes[i];
+            if(highest_only) break;
+        }
+    }
+    buffer[j] = '\0';
+    return j;
+}
+
 static void ircclient_recv(struct IRCClient *client, char *line) {
     struct UserSession *session = client->session;
     char *argv[MAXNUMPARAMS];
@@ -125,6 +287,10 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
             session->nick = strdup(argv[2]);
             client->fully_connected = 1;
         }
+        if(!stricmp(argv[1], "005")) {
+            //parse 005 raw
+            ircclient_raw005_parse(client, argv+3, argc-3);
+        }
         if(!stricmp(argv[1], "001") ||
             !stricmp(argv[1], "002") ||
             !stricmp(argv[1], "003") ||
@@ -149,24 +315,33 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
             }
         } else if(!stricmp(argv[1], "JOIN")) {
             struct IRCUser from = ircclient_parse_user(argv[0]);
+            struct IRCChannel *channel;
             if(!stricmp(from.nick, session->nick)) {
-                struct IRCChannel *lchannel = NULL, *channel = malloc(sizeof(*channel));
+                struct IRCChannel *lchannel = NULL;
+                channel = calloc(1, sizeof(*channel));
                 if(client->channel)
                     for(lchannel = client->channel; lchannel->next; lchannel = lchannel->next) {}
                 channel->name = strdup(argv[2]);
-                channel->next = NULL;
                 channel->prev = lchannel;
                 if(lchannel)
                     lchannel->next = channel;
                 else
                     client->channel = channel;
+            } else {
+                for(channel = client->channel; channel; channel = channel->next) {
+                    if(!stricmp(channel->name, argv[2])) {
+                        ircclient_userlist_add(channel, from.nick);
+                        break;
+                    }
+                }
             }
         } else if(!stricmp(argv[1], "PART")) {
             struct IRCUser from = ircclient_parse_user(argv[0]);
-            if(!stricmp(from.nick, session->nick)) {
-                struct IRCChannel *channel;
-                for(channel = client->channel; channel; channel = channel->next) {
-                    if(!stricmp(channel->name, argv[2])) {
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[2])) {
+                    if(!stricmp(from.nick, session->nick)) {
+                        ircclient_userlist_clear(channel);
                         if(channel->prev)
                             channel->prev->next = channel->next;
                         else
@@ -175,15 +350,18 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
                             channel->next->prev = channel->prev;
                         free(channel->name);
                         free(channel);
-                        break;
+                    } else {
+                        ircclient_userlist_del(channel, from.nick);
                     }
+                    break;
                 }
             }
         } else if(!stricmp(argv[1], "KICK")) {
-            if(!stricmp(argv[3], session->nick)) {
-                struct IRCChannel *channel;
-                for(channel = client->channel; channel; channel = channel->next) {
-                    if(!stricmp(channel->name, argv[2])) {
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[2])) {
+                    if(!stricmp(argv[3], session->nick)) {
+                        ircclient_userlist_clear(channel);
                         if(channel->prev)
                             channel->prev->next = channel->next;
                         else
@@ -192,8 +370,10 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
                             channel->next->prev = channel->prev;
                         free(channel->name);
                         free(channel);
-                        break;
+                    } else {
+                        ircclient_userlist_del(channel, argv[3]);
                     }
+                    break;
                 }
             }
         } else if(!stricmp(argv[1], "NICK")) {
@@ -202,6 +382,66 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
                 free(session->nick);
                 session->nick = strdup(argv[2]);
             }
+            struct IRCChannel *channel;
+            struct IRCChannelMember *member;
+            for(channel = client->channel; channel; channel = channel->next) {
+                for(member = channel->userlist; member; member = member->next) {
+                    if(!stricmp(member->nick, from.nick)) {
+                        free(member->nick);
+                        member->nick = strdup(argv[2]);
+                        break;
+                    }
+                }
+            }
+        } else if(!stricmp(argv[1], "MODE")) {
+            if(!stricmp(argv[2], session->nick)) {
+                //user mode
+            } else if(ircclient_is_channel_prefix(client, argv[2][0])) {
+                struct IRCChannel *channel;
+                struct IRCChannelMember *member;
+                for(channel = client->channel; channel; channel = channel->next) {
+                    for(member = channel->userlist; member; member = member->next) {
+                        ircclient_chanmode_parse(client, channel, argv[3], argv+4, argc-4);
+                    }
+                }
+            }
+        } else if(!stricmp(argv[1], "353")) {
+            struct IRCChannel *channel;
+            struct IRCChannelMember *member;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[4])) {
+                    if(!channel->synchronizing_userlist) {
+                        channel->synchronizing_userlist = 1;
+                        ircclient_userlist_clear(channel);
+                    }
+                    char *a = argv[5];
+                    char *b, *c;
+                    do {
+                        b = strchr(a, ' ');
+                        if(b) *b = '\0';
+                        if((c = strchr(a, '!'))) {
+                            *c = '\0';
+                        }
+                        int prefix_id;
+                        int member_modes = 0;
+                        while(ircclient_is_chanuser_prefix(client, *a, &prefix_id)) {
+                            member_modes |= (1 << prefix_id);
+                            a++;
+                        }
+                        member = ircclient_userlist_add(channel, a);
+                        member->modes = member_modes;
+                        if(b) a = b+1;
+                    } while(b);
+                    break;
+                }
+            }
+        } else if(!stricmp(argv[1], "366")) {
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[3])) {
+                    channel->synchronizing_userlist = 0;
+                }
+            }
         }
     }
     if(pass_to_client)
@@ -240,12 +480,28 @@ void ircclient_recover_session(struct UserSession *session) {
         usersession_client_raw(session, recover_line->line);
     }
     struct IRCChannel *channel;
+    struct IRCChannelMember *member;
     char raw[LINELEN];
+    int prefixlen = strlen((client->network_prefixes ? client->network_prefixes : "@+"));
+    char prefixbuf[prefixlen+1];
     for(channel = client->channel; channel; channel = channel->next) {
         sprintf(raw, ":%s!%s@TransparentIRC.session.recover JOIN %s", client->session->nick, client->session->username, channel->name);
         usersession_client_raw(session, raw);
-        
-        sprintf(raw, "NAMES %s", channel->name);
-        ircclient_send(client, raw);
+        //replay /NAMES
+        int headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
+        int first_element = 1;
+        for(member = channel->userlist; member; member = member->next) {
+            if(headerlen + strlen(member->nick) + prefixlen + 1 >= LINELEN-2) {
+                usersession_client_raw(session, raw);
+                headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
+                first_element = 1;
+            }
+            ircclient_build_chanuser_prefix(client, member, prefixbuf, 1);
+            headerlen += sprintf(raw+headerlen, "%s%s%s", (first_element ? "" : " "), prefixbuf, member->nick);
+            first_element = 0;
+        }
+        if(!first_element) {
+            usersession_client_raw(session, raw);
+        }
     }
 }
index 707400b968d261deef6a07749cfd3e9d8d91f32a..ec254f63c5841bda2fbbd24033b426f64946fd6d 100644 (file)
@@ -23,8 +23,17 @@ struct IODescriptor;
 struct UserSession;
 struct UserLogin;
 
+struct IRCChannelMember {
+    char *nick;
+    int modes;
+    
+    struct IRCChannelMember *next, *prev;
+};
+
 struct IRCChannel {
     char *name;
+    struct IRCChannelMember *userlist;
+    int synchronizing_userlist : 1;
     
     struct IRCChannel *next, *prev;
 };
@@ -44,6 +53,11 @@ struct IRCClient {
     struct IRCLine *recover_header;
     struct IRCChannel *channel;
     
+    char *network_prefixes;
+    char *network_prefixes_char;
+    char *network_chanmodes;
+    char *network_chantypes;
+    
     struct IRCClient *next, *prev;
 };