$ git clone https://tcclient.ion.nu/tc_client.git
commit 14a29f9cb88c5d6d35e18b5cfc3f9f623f85623a
Author: Alicia <...>
Date:   Tue Apr 7 06:49:00 2015 +0200

    Version 0.5

diff --git a/ChangeLog b/ChangeLog
index f9308a2..adedc1a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+0.5:
+Fixed a bug in irchack.c that sent the JOIN confirmation to the wrong file descriptor.
+Avoid sending empty lines.
+Handle messages related to getting kicked out/banned from a channel.
 0.4:
 Adjustments for portability: nested functions are a GCC extension and should not be used, include headers needed on other systems.
 Use memcpy in some places instead of *x=y and x=*y to avoid alignment issues.
diff --git a/Makefile b/Makefile
index 269d2d2..7f57a99 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,12 @@
-VERSION=0.4
+VERSION=0.5
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
 LIBS=-g3 $(shell curl-config --libs)
 
-tc_client: client.o amfparser.o rtmp.o numlist.o amfwriter.o
+tc_client: client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o
  $(CC) $(LDFLAGS) $^ $(LIBS) -o $@
 
 clean:
- rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o tc_client
+ rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o tc_client
 
 tarball:
- tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c amfparser.h rtmp.h numlist.h amfwriter.h LICENSE README ChangeLog irchack.c
+ tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h LICENSE README ChangeLog irchack.c
diff --git a/client.c b/client.c
index 7b8978f..40f59af 100644
--- a/client.c
+++ b/client.c
@@ -1,6 +1,7 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -29,6 +30,7 @@
 #include "amfparser.h"
 #include "numlist.h"
 #include "amfwriter.h"
+#include "idlist.h"
 
 struct writebuf
 {
@@ -124,7 +126,7 @@ char* getkey(char* id, char* channel)
 {
   char url[strlen("http://tinychat.com/api/captcha/check.php?guest%5Fid=&room=tinychat%5E0")+strlen(id)+strlen(channel)];
   sprintf(url, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%s&room=tinychat%%5E%s", id, channel);
-  char *response=http_get(url);
+  char* response=http_get(url);
   char* key=strstr(response, "\"key\":\"");
 
   if(!key){return 0;}
@@ -142,6 +144,16 @@ char* getkey(char* id, char* channel)
   return key;
 }
 
+char timestampbuf[8];
+char* timestamp()
+{
+  // Timestamps, e.g. "[hh:mm] name: message"
+  time_t timestamp=time(0);
+  struct tm* t=localtime(&timestamp);
+  sprintf(timestampbuf, "[%02i:%02i]", t->tm_hour, t->tm_min);
+  return timestampbuf;
+}
+
 int main(int argc, char** argv)
 {
   setlocale(LC_ALL, "");
@@ -280,6 +292,7 @@ int main(int argc, char** argv)
       pfd[0].revents=0;
       len=read(0, buf, 2047);
       while(len>0 && (buf[len-1]=='\n'||buf[len-1]=='\r')){--len;}
+      if(!len){continue;} // Don't send empty lines
       buf[len]=0;
       len=mbstowcs(0, (char*)buf, 0);
       wchar_t wcsbuf[len+1];
@@ -303,7 +316,7 @@ int main(int argc, char** argv)
 // TODO: This should be done differently, first reading one byte to get the size of the header, then read the rest of the header and thus get the length of the content
     len=read(sock, buf, 2048);
 //  printf("Received %i byte response\n", len);
-    if(!len){printf("Server disconnected\n"); break;}
+    if(len<=0){printf("Server disconnected\n"); break;}
 /* Debugging
   char name[128];
   sprintf(name, "output%i", outnum);
@@ -349,10 +362,7 @@ int main(int argc, char** argv)
       else if(amfin->itemcount>5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "privmsg") && amfin->items[3].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
       {
         wchar_t* msg=fromnumlist(amfin->items[3].string.string);
-        // Timestamps, e.g. "[hh:mm] name: message"
-        time_t timestamp=time(0);
-        struct tm* t=localtime(&timestamp);
-        printf("[%02i:%02i] %s: %ls\n", t->tm_hour, t->tm_min, amfin->items[5].string.string, msg);
+        printf("%s %s: %ls\n", timestamp(), amfin->items[5].string.string, msg);
         free(msg);
         fflush(stdout);
       }
@@ -364,41 +374,71 @@ int main(int argc, char** argv)
         for(i = 3; i < amfin->itemcount-1; i+=2)
         {
           // a "numeric" id precedes each nick, i.e. i is the id, i+1 is the nick
-          if(amfin->items[i+1].type==AMF_STRING)
+          if(amfin->items[i].type==AMF_STRING && amfin->items[i+1].type==AMF_STRING)
           {
+            idlist_add(atoi(amfin->items[i].string.string), amfin->items[i+1].string.string);
             printf("%s%s", (i==3?"":", "), amfin->items[i+1].string.string);
           }
         }
         printf("\n");
         fflush(stdout);
       }
-      // join
-      else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "join") && amfin->items[3].type==AMF_STRING)
+      // join ("join", 0, "<ID>", "guest-<ID>")
+      else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "join") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
       {
-        // Timestamps, e.g. "[hh:mm] name: message"
-        time_t timestamp=time(0);
-        struct tm* t=localtime(&timestamp);
-        printf("[%02i:%02i] %s entered the channel\n", t->tm_hour, t->tm_min, amfin->items[3].string.string);
+        idlist_add(atoi(amfin->items[2].string.string), amfin->items[3].string.string);
+        printf("%s %s entered the channel\n", timestamp(), amfin->items[3].string.string);
         fflush(stdout);
       }
       // part
       else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "quit") && amfin->items[3].type==AMF_STRING)
       {
-        // Timestamps, e.g. "[hh:mm] name: message"
-        time_t timestamp=time(0);
-        struct tm* t=localtime(&timestamp);
-        printf("[%02i:%02i] %s left the channel\n", t->tm_hour, t->tm_min, amfin->items[2].string.string);
+        idlist_remove(amfin->items[2].string.string);
+        printf("%s %s left the channel\n", timestamp(), amfin->items[2].string.string);
         fflush(stdout);
       }
       // nick
       else if(amfin->itemcount==5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nick") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
       {
-        // Timestamps, e.g. "[hh:mm] name: message"
-        time_t timestamp=time(0);
-        struct tm* t=localtime(&timestamp);
-        printf("[%02i:%02i] %s changed nickname to %s\n", t->tm_hour, t->tm_min, amfin->items[2].string.string, amfin->items[3].string.string);
+        idlist_rename(amfin->items[2].string.string, amfin->items[3].string.string);
+        printf("%s %s changed nickname to %s\n", timestamp(), amfin->items[2].string.string, amfin->items[3].string.string);
         fflush(stdout);
       }
+      // kick
+      else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "kick") && amfin->items[2].type==AMF_STRING)
+      {
+        if(atoi(amfin->items[2].string.string) == idlist_get(nickname))
+        {
+          printf("%s You have been kicked out\n", timestamp());
+          fflush(stdout);
+        }
+      }
+      // banned
+      else if(amfin->itemcount==2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "banned"))
+      {
+        printf("%s You are banned from %s\n", timestamp(), channel);
+        fflush(stdout);
+        // When banned and reconnecting, tinychat doesn't disconnect us itself, we need to disconnect
+        close(sock);
+        return 1; // Getting banned is a failure, right?
+      }
+      // from_owner: notices
+      else if(amfin->itemcount==3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "from_owner") && amfin->items[2].type==AMF_STRING)
+      {
+        if(!strncmp("notice", amfin->items[2].string.string, 6))
+        {
+          char* notice=strdup(&amfin->items[2].string.string[6]);
+          // replace "%20" with spaces
+          char* space;
+          while((space=strstr(notice, "%20")))
+          {
+            memmove(space, &space[2], strlen(&space[2])+1);
+            space[0]=' ';
+          }
+          printf("%s %s\n", timestamp(), notice);
+          fflush(stdout);
+        }
+      }
       // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
       amf_free(amfin);
     }
diff --git a/idlist.c b/idlist.c
index 8cc0e16..5e7c5f9 100644
--- a/idlist.c
+++ b/idlist.c
@@ -1,7 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014  alicia@ion.nu
-    Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -40,7 +39,7 @@ void idlist_remove(const char* name)
     {
       free((void*)idlist[i].name);
       --idlistlen;
-      memmove(&idlist[i], &idlist[i+1], sizeof(struct idmap)*(idlistlen-i));
+      memmove(idlist, &idlist[i+1], sizeof(struct idmap)*(idlistlen-i));
       return;
     }
   }
@@ -62,41 +61,13 @@ void idlist_rename(const char* oldname, const char* newname)
 
 int idlist_get(const char* name)
 {
-  int len;
-  for(len=0; name[len]&&name[len]!=' '; ++len);
   int i;
   for(i=0; i<idlistlen; ++i)
   {
-    if(!strncmp(name, idlist[i].name, len) && !idlist[i].name[len])
+    if(!strcmp(name, idlist[i].name))
     {
       return idlist[i].id;
     }
   }
   return -1;
 }
-
-void idlist_set_op(const char* name, char op)
-{
-  int i;
-  for(i=0; i<idlistlen; ++i)
-  {
-    if(!strcmp(name, idlist[i].name))
-    {
-      idlist[i].op=op;
-      return;
-    }
-  }
-}
-
-char idlist_is_op(const char* name)
-{
-  int i;
-  for(i=0; i<idlistlen; ++i)
-  {
-    if(!strcmp(name, idlist[i].name))
-    {
-      return idlist[i].op;
-    }
-  }
-  return 0;
-}
diff --git a/idlist.h b/idlist.h
index b7c7f81..3a04b6a 100644
--- a/idlist.h
+++ b/idlist.h
@@ -1,7 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014  alicia@ion.nu
-    Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -17,9 +16,8 @@
 */
 struct idmap
 {
-  const char* name;
   int id;
-  char op;
+  const char* name;
 };
 
 extern struct idmap* idlist;
@@ -29,5 +27,3 @@ extern void idlist_add(int id, const char* name);
 extern void idlist_remove(const char* name);
 extern void idlist_rename(const char* oldname, const char* newname);
 extern int idlist_get(const char* name);
-extern void idlist_set_op(const char* name, char op);
-extern char idlist_is_op(const char* name);
diff --git a/irchack.c b/irchack.c
index 389ebb9..e64ae9d 100644
--- a/irchack.c
+++ b/irchack.c
@@ -102,7 +102,7 @@ int main()
   }
   close(tc_in[0]);
   close(tc_out[1]);
-  dprintf(tc_in[1], ":%s!user@host JOIN %s\n", nick, channel);
+  dprintf(sock, ":%s!user@host JOIN #%s\n", nick, channel);
   struct pollfd pfd[2];
   pfd[0].fd=tc_out[0];
   pfd[0].events=POLLIN;