$ git clone http://tcclient.ion.nu/tc_client.git
commit 48af5473dd52e2d641e68859b8b816d17b582355
Author: Alicia <...>
Date:   Tue Apr 7 06:49:01 2015 +0200

    Version 0.25

diff --git a/ChangeLog b/ChangeLog
index d5c1eaa..317ada6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+0.25:
+Handle short reads in the RTMP headers too.
+Added the option -c/--color <number> to pick color instead of getting a random color at startup (contributed by Pamela)
+cursedchat: use default colors (for background in particular)
+cursedchat: added scrolling.
+modbot: fixed singular form of 'videos' in !queue response.
+modbot: update 'requested' (for !requestedby) also when a video is played manually.
+camviewer: added basic mic code (mixing is still missing so it is disabled by default, to enable, configure with environment variable ENABLE_MIC set to 1)
 0.24:
 Fixed endianness of RTMP set-chunk-size handling.
 modbot: fixed a bug where '!wrongrequest' would claim no request was found when (and only when) the request was last in queue.
diff --git a/Makefile b/Makefile
index 9735d2d..0f7e575 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.24
+VERSION=0.25
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
 LIBS=-g3 $(shell curl-config --libs)
 ifneq ($(wildcard config.mk),)
@@ -16,6 +16,11 @@ ifdef AVUTIL_LIBS
 ifdef SWSCALE_LIBS
   UTILS+=camviewer
   CFLAGS+=$(GTK_CFLAGS) $(AVCODEC_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
+  ifdef SWRESAMPLE_LIBS
+  ifdef AO_LIBS
+    CFLAGS+=-DHAVE_SOUND $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
+  endif
+  endif
 endif
 endif
 endif
@@ -39,7 +44,7 @@ modbot: $(MODBOT_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) -o $@
 
 camviewer: $(CAMVIEWER_OBJ)
- $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) -o $@
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) -o $@
 
 cursedchat: $(CURSEDCHAT_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
diff --git a/client.c b/client.c
index 9cd746d..3a3b5fa 100644
--- a/client.c
+++ b/client.c
@@ -2,6 +2,7 @@
     tc_client, a simple non-flash client for tinychat(.com)
     Copyright (C) 2014-2015  alicia@ion.nu
     Copyright (C) 2014-2015  Jade Lea
+    Copyright (C) 2015  Pamela Hiatt
 
     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
@@ -210,6 +211,7 @@ void usage(const char* me)
          "-h, --help           Show this help text and exit\n"
          "-u, --user <user>    Username of tinychat account to use.\n"
          "-p, --pass <pass>    Password of tinychat account to use.\n"
+         "-c, --color <value>  Color to use in chat.\n"
          ,me);
 }
 
@@ -238,6 +240,11 @@ int main(int argc, char** argv)
       ++i;
       account_pass=strdup(argv[i]);
     }
+    else if(!strcmp(argv[i], "-c")||!strcmp(argv[i], "--color"))
+    {
+      ++i;
+      currentcolor=atoi(argv[i]);
+    }
     else if(!channel){channel=argv[i];}
     else if(!nickname){nickname=strdup(argv[i]);}
     else if(!password){password=argv[i];}
@@ -245,7 +252,7 @@ int main(int argc, char** argv)
   // Check for required arguments
   if(!channel||!nickname){usage(argv[0]); return 1;}
   char badchar;
-  if((badchar=checknick(argv[2])))
+  if((badchar=checknick(nickname)))
   {
     printf("'%c' is not allowed in nicknames.\n", badchar);
     return 1;
@@ -293,7 +300,10 @@ int main(int argc, char** argv)
   // RTMP handshake
   unsigned char handshake[1536];
   read(random, handshake, 1536);
-  read(random, &currentcolor, sizeof(currentcolor));
+  if(currentcolor>=COLORCOUNT)
+  {
+    read(random, &currentcolor, sizeof(currentcolor));
+  }
   close(random);
   write(sock, "\x03", 1); // Send 0x03 and 1536 bytes of random junk
   write(sock, handshake, 1536);
@@ -415,9 +425,9 @@ int main(int argc, char** argv)
             if(!strcmp(&buf[7], "off")){showcolor=0; continue;}
             if(!strcmp(&buf[7], "on")){showcolor=1; continue;}
             currentcolor=atoi(&buf[7]);
-            printf("\x1b[%smChanged color\x1b[0m\n", termcolors[currentcolor%16]);
+            printf("\x1b[%smChanged color\x1b[0m\n", termcolors[currentcolor%COLORCOUNT]);
           }else{ // No color specified, state our current color
-            printf("\x1b[%smCurrent color: %i\x1b[0m\n", termcolors[currentcolor%16], currentcolor%16);
+            printf("\x1b[%smCurrent color: %i\x1b[0m\n", termcolors[currentcolor%COLORCOUNT], currentcolor%COLORCOUNT);
           }
           fflush(stdout);
           continue;
@@ -425,7 +435,7 @@ int main(int argc, char** argv)
         else if(!strcmp(buf, "/colors"))
         {
           int i;
-          for(i=0; i<16; ++i)
+          for(i=0; i<COLORCOUNT; ++i)
           {
             printf("\x1b[%smColor %i\x1b[0m\n", termcolors[i], i);
           }
@@ -553,7 +563,7 @@ int main(int argc, char** argv)
       amfnum(&amf, 0);
       amfnull(&amf);
       amfstring(&amf, msg);
-      amfstring(&amf, colors[currentcolor%16]);
+      amfstring(&amf, colors[currentcolor%COLORCOUNT]);
       // For PMs, add a string like "n<numeric ID>-<nick>" to make the server only send it to the intended recipient
       if(privfield)
       {
diff --git a/colors.c b/colors.c
index 6812d67..28969eb 100644
--- a/colors.c
+++ b/colors.c
@@ -1,6 +1,7 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
+    Copyright (C) 2015  Pamela Hiatt
 
     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
@@ -18,7 +19,7 @@
 #include "colors.h"
 
 // Sorted like rainbows
-const char* colors[]={ // The 16 colors accepted by the flash client
+const char* colors[COLORCOUNT]={ // The 16 colors accepted by the flash client
   "#821615,en",
   "#c53332,en",
   "#a08f23,en",
@@ -37,7 +38,7 @@ const char* colors[]={ // The 16 colors accepted by the flash client
   "#b9807f,en"
 };
 
-const char* termcolors[]={ // Equivalent color codes for ANSI escape sequences
+const char* termcolors[COLORCOUNT]={ // Equivalent color codes for ANSI escape sequences
   "31",
   "31;1",
   "33",
@@ -56,12 +57,12 @@ const char* termcolors[]={ // Equivalent color codes for ANSI escape sequences
   "35;1"
 };
 
-unsigned int currentcolor;
+unsigned int currentcolor=COLORCOUNT;
 
 const char* resolvecolor(const char* tc_color)
 {
   int i;
-  for(i=0; i<16; ++i)
+  for(i=0; i<COLORCOUNT; ++i)
   {
     if(!strcmp(colors[i], tc_color)){return termcolors[i];}
   }
diff --git a/colors.h b/colors.h
index a57cee2..baa5d9b 100644
--- a/colors.h
+++ b/colors.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
 
     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
@@ -14,6 +14,7 @@
     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
+#define COLORCOUNT 16
 extern const char* colors[];
 extern const char* termcolors[];
 extern unsigned int currentcolor;
diff --git a/configure b/configure
index 4d62ba9..28bb16d 100755
--- a/configure
+++ b/configure
@@ -87,6 +87,29 @@ else
   echo no
 fi
 
+# TODO: Figure out how to mix sound sources so that this doesn't sound horrible with more than one person on mic, having it disabled by default until then
+if [ "x${ENABLE_MIC}" != "x" ]; then
+printf 'Checking for libswresample... '
+libs="`pkg-config --libs libswresample 2> /dev/null`"
+if [ "x$libs" != "x" ]; then
+  echo "SWRESAMPLE_LIBS=${libs}" >> config.mk
+  echo "SWRESAMPLE_CFLAGS=`pkg-config --cflags libswresample`" >> config.mk
+  echo yes
+else
+  echo no
+fi
+
+printf 'Checking for libao... '
+libs="`pkg-config --libs ao 2> /dev/null`"
+if [ "x$libs" != "x" ]; then
+  echo "AO_LIBS=${libs}" >> config.mk
+  echo "AO_CFLAGS=`pkg-config --cflags ao`" >> config.mk
+  echo yes
+else
+  echo no
+fi
+fi
+
 printf 'Checking for ncurses... '
 libs="`ncursesw5-config --libs 2> /dev/null || ncurses5-config --libs 2> /dev/null`"
 if [ "x$libs" != "x" ]; then
diff --git a/rtmp.c b/rtmp.c
index 4cb900d..035ff7a 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -35,6 +35,19 @@ struct chunk* chunks=0;
 unsigned int chunkcount=0;
 unsigned int chunksize_in=128;
 
+size_t fullread(int fd, void* buf, size_t len)
+{
+  size_t remaining=len;
+  while(remaining>0)
+  {
+    size_t r=read(fd, buf, remaining);
+    if(r<1){return 0;}
+    remaining-=r;
+    buf+=r;
+  }
+  return len;
+}
+
 struct chunk* chunk_get(unsigned int id)
 {
   unsigned int i;
@@ -58,40 +71,40 @@ char rtmp_get(int sock, struct rtmp* rtmp)
 {
   // Header format and chunk ID
   unsigned int x=0;
-  if(read(sock, &x, 1)<1){return 0;}
+  if(fullread(sock, &x, 1)<1){return 0;}
   unsigned int chunkid=x&0x3f;
   unsigned int fmt=(x&0xc0)>>6;
   struct chunk* chunk=chunk_get(chunkid);
   // Handle extended stream IDs
   if(chunkid<2) // (0=1 extra byte, 1=2 extra bytes)
   {
-    read(sock, &x, chunkid+1);
+    fullread(sock, &x, chunkid+1);
     chunkid=64+x;
   }
   if(fmt<3)
   {
     // Timestamp
-    read(sock, &x, 3);
+    fullread(sock, &x, 3);
     chunk->timestamp=x;
     if(fmt<2)
     {
       // Length
       x=0;
-      read(sock, ((void*)&x)+1, 3);
+      fullread(sock, ((void*)&x)+1, 3);
       chunk->length=be32(x);
       // Type
-      read(sock, &chunk->type, sizeof(chunk->type));
+      fullread(sock, &chunk->type, sizeof(chunk->type));
       if(fmt<1)
       {
         // Message ID
-        read(sock, &chunk->streamid, sizeof(chunk->streamid));
+        fullread(sock, &chunk->streamid, sizeof(chunk->streamid));
       }
     }
   }
   // Extended timestamp
   if(chunk->timestamp==0xffffff)
   {
-    read(sock, &x, sizeof(x));
+    fullread(sock, &x, sizeof(x));
     chunk->timestamp=be32(x);
   }
 
@@ -101,14 +114,7 @@ char rtmp_get(int sock, struct rtmp* rtmp)
     chunk->pos=0;
   }
   unsigned int rsize=((chunk->length-chunk->pos>=chunksize_in)?chunksize_in:(chunk->length-chunk->pos));
-  while(rsize>0)
-  {
-    size_t r=read(sock, chunk->buf+chunk->pos, rsize);
-    if(r<1){return 0;}
-    rsize-=r;
-    chunk->pos+=r;
-//    if(rsize){printf("Got a short read, %u remaining\n", rsize);}
-  }
+  chunk->pos+=fullread(sock, chunk->buf+chunk->pos, rsize);
   if(chunk->pos==chunk->length)
   {
     if(chunk->type==RTMP_SET_PACKET_SIZE)
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index a1a8a6b..1f9330c 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -18,6 +18,10 @@
 #include <fcntl.h>
 #include <libavcodec/avcodec.h>
 #include <libswscale/swscale.h>
+#ifdef HAVE_SOUND
+#include <libswresample/swresample.h>
+#include <ao/ao.h>
+#endif
 #include <gtk/gtk.h>
 
 #if GTK_MAJOR_VERSION==2
@@ -39,7 +43,8 @@ struct camera
   AVFrame* frame;
   AVFrame* dstframe;
   GtkWidget* cam;
-  AVCodecContext* ctx;
+  AVCodecContext* vctx;
+  AVCodecContext* actx;
   char* id;
   char* nick;
   GtkWidget* box; // holds label and cam
@@ -50,7 +55,11 @@ struct viddata
   struct camera* cams;
   unsigned int camcount;
   GtkWidget* box;
-  AVCodec* decoder;
+  AVCodec* vdecoder;
+  AVCodec* adecoder;
+#ifdef HAVE_SOUND
+  int audiopipe;
+#endif
 };
 
 int tc_client[2];
@@ -106,8 +115,12 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
     cam->dstframe=av_frame_alloc();
     cam->nick=strdup(nick);
     cam->id=strdup(id);
-    cam->ctx=avcodec_alloc_context3(data->decoder);
-    avcodec_open2(cam->ctx, data->decoder, 0);
+    cam->vctx=avcodec_alloc_context3(data->vdecoder);
+    avcodec_open2(cam->vctx, data->vdecoder, 0);
+#ifdef HAVE_SOUND
+    cam->actx=avcodec_alloc_context3(data->adecoder);
+    avcodec_open2(cam->actx, data->adecoder, 0);
+#endif
     cam->cam=gtk_image_new();
     cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
     gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
@@ -124,7 +137,10 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
       {
         gtk_widget_destroy(data->cams[i].box);
         av_frame_free(&data->cams[i].frame);
-        avcodec_free_context(&data->cams[i].ctx);
+        avcodec_free_context(&data->cams[i].vctx);
+#ifdef HAVE_SOUND
+        avcodec_free_context(&data->cams[i].actx);
+#endif
         free(data->cams[i].id);
         free(data->cams[i].nick);
         --data->camcount;
@@ -138,20 +154,42 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
   {
     char* sizestr=strchr(&buf[7], ' ');
     if(!sizestr){return 1;}
+    sizestr[0]=0;
     unsigned int size=strtoul(&sizestr[1], 0, 0);
     if(!size){return 1;}
     unsigned char frameinfo;
     read(tc_client[0], &frameinfo, 1);
     --size; // For the byte we read above
-    unsigned char buf[size];
+    AVPacket pkt;
+    av_init_packet(&pkt);
+    unsigned char databuf[size];
+    pkt.data=databuf;
+    pkt.size=size;
     unsigned int pos=0;
     while(pos<size)
     {
-      pos+=read(tc_client[0], &buf[pos], size-pos);
+      pos+=read(tc_client[0], pkt.data+pos, size-pos);
     }
-// TODO: decode and send to a sound lib (libao)
+#ifdef HAVE_SOUND
+    // Find the camera representation for the given ID (for decoder context)
+    struct camera* cam=0;
+    for(i=0; i<data->camcount; ++i)
+    {
+      if(!strcmp(data->cams[i].id, &buf[7])){cam=&data->cams[i]; break;}
+    }
+    if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); free(pkt.data); return 1;}
+    int gotframe;
+    avcodec_decode_audio4(cam->actx, cam->frame, &gotframe, &pkt);
+    if(!gotframe){return 1;}
+    SwrContext* swrctx=swr_alloc_set_opts(0, 1, AV_SAMPLE_FMT_S16, 22050, 1, cam->frame->format, 11025, 0, 0); // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
+    swr_init(swrctx);
+    int outlen=swr_convert(swrctx, cam->frame->data, cam->frame->nb_samples, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
+    swr_free(&swrctx);
+    write(data->audiopipe, cam->frame->data[0], outlen);
+#endif
+    return 1;
   }
-  if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); return 1;} // Ignore anything else that isn't video
+  if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); fflush(stdout); return 1;} // Ignore anything else that isn't video
   char* sizestr=strchr(&buf[7], ' ');
   if(!sizestr){return 1;}
   sizestr[0]=0;
@@ -182,7 +220,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
   if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); free(pkt.data); return 1;}
   pkt.size=size;
   int gotframe;
-  avcodec_decode_video2(cam->ctx, cam->frame, &gotframe, &pkt);
+  avcodec_decode_video2(cam->vctx, cam->frame, &gotframe, &pkt);
   if(!gotframe){return 1;}
 
   // Convert to RGB24 format
@@ -203,11 +241,48 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
   return 1;
 }
 
+#ifdef HAVE_SOUND
+void audiothread(int fd)
+{
+  ao_initialize();
+  ao_sample_format samplefmt;
+  samplefmt.bits=16;
+  samplefmt.rate=22050;
+  samplefmt.channels=1;
+  samplefmt.byte_format=AO_FMT_NATIVE; // I'm guessing libavcodec decodes it to native
+  samplefmt.matrix=0;
+  ao_option clientname={.key="client_name", .value="tc_client/camviewer", .next=0};
+  ao_device* dev=ao_open_live(ao_default_driver_id(), &samplefmt, &clientname);
+// TODO: mix sounds, somehow..
+  char buf[2048];
+  size_t len;
+  while((len=read(fd, buf, 2048))>0)
+  {
+    ao_play(dev, buf, len);
+  }
+  ao_close(dev);
+}
+#endif
+
 int main(int argc, char** argv)
 {
-  struct viddata data={0,0,0,0};
+  struct viddata data={0,0,0,0,0};
   avcodec_register_all();
-  data.decoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
+  data.vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
+  data.adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
+
+#ifdef HAVE_SOUND
+  int audiopipe[2];
+  pipe(audiopipe);
+  data.audiopipe=audiopipe[1];
+  if(!fork())
+  {
+    close(audiopipe[1]);
+    audiothread(audiopipe[0]);
+    _exit(0);
+  }
+  close(audiopipe[0]);
+#endif
 
   gtk_init(&argc, &argv);
   GtkWidget* w=gtk_window_new(GTK_WINDOW_TOPLEVEL);
@@ -234,14 +309,17 @@ int main(int argc, char** argv)
   unsigned int channel_id=g_io_add_watch(tcchannel, G_IO_IN, handledata, &data);
 
   gtk_main();
-

   g_source_remove(channel_id);
   g_io_channel_shutdown(tcchannel, 0, 0);
   unsigned int i;
   for(i=0; i<data.camcount; ++i)
   {
     av_frame_free(&data.cams[i].frame);
-    avcodec_free_context(&data.cams[i].ctx);
+    avcodec_free_context(&data.cams[i].vctx);
+#ifdef HAVE_SOUND
+    avcodec_free_context(&data.cams[i].actx);
+#endif
     free(data.cams[i].id);
     free(data.cams[i].nick);
   }
diff --git a/utilities/cursedchat/cursedchat.c b/utilities/cursedchat/cursedchat.c
index 5da726a..fe00302 100644
--- a/utilities/cursedchat/cursedchat.c
+++ b/utilities/cursedchat/cursedchat.c
@@ -23,9 +23,11 @@
 #include <curses.h>
 #include <readline/readline.h>
 
+#define HALFSCREEN (LINES>4?(LINES-3)/2:1)
 WINDOW* topic;
 WINDOW* chat;
 WINDOW* input;
+int chatscroll=-1;
 int to_app;
 
 // Translate ANSI escape codes to curses commands and write the text to a window
@@ -71,6 +73,11 @@ void waddansi(WINDOW* w, char* str)
   }
 }
 
+void drawchat(void)
+{
+  prefresh(chat, (chatscroll>-1?chatscroll:getcury(chat)-LINES+4), 0, 1, 0, LINES-3, COLS);
+}
+
 void gotline(char* line)
 {
   if(!line){close(to_app); return;} // TODO: handle EOF on stdin better?
@@ -80,7 +87,7 @@ void gotline(char* line)
   write(to_app, "\n", 1);
 // TODO: grab user's nick for this
   wprintw(chat, "\n%s: %s", "You", line);
-  wrefresh(chat);
+  drawchat();
 }
 
 unsigned int bytestochars(const char* buf, unsigned int buflen, unsigned int bytes)
@@ -117,27 +124,29 @@ int escinput(int a, int byte)
   if(!strcmp(buf, "[H")||!strcmp(buf, "OH")){rl_beg_of_line(1,27);return 0;}
   if(!strcmp(buf, "[F")||!strcmp(buf, "OF")){rl_end_of_line(1,27);return 0;}
   if(!strcmp(buf, "[3")&&read(0, buf, 1)&&buf[0]=='~'){rl_delete(1,27);return 0;}
-  if(!strcmp(buf, "[5"))
+  if(!strcmp(buf, "[5")) // Page up
   {
     read(0, buf, 1);
-    wprintw(chat, "\nTODO: handle Page up");
-// wscrl(chat, chat->_maxy/2);
-    wrefresh(chat);
+    if(chatscroll<0){chatscroll=getcury(chat)-LINES+4;}
+    chatscroll-=HALFSCREEN; // (LINES-3)/2;
+    if(chatscroll<0){chatscroll=0;}
+    drawchat();
     return 0;
   }
-  if(!strcmp(buf, "[6"))
+  if(!strcmp(buf, "[6")) // Page down
   {
     read(0, buf, 1);
-    wprintw(chat, "\nTODO: handle Page down");
-// wscrl(chat, -chat->_maxy/2);
-    wrefresh(chat);
+    if(chatscroll<0){return 0;} // Already at the bottom
+    chatscroll+=HALFSCREEN; // (LINES-3)/2;
+    if(chatscroll>getcury(chat)-LINES+3){chatscroll=-1;}
+    drawchat();
     return 0;
   }
 //  wprintw(chat, "\nbuf: %s", buf);
   return 0;
 }
 
-void drawinput()
+void drawinput(void)
 {
   werase(input);
   unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
@@ -160,15 +169,14 @@ void resizechat(int sig)
   if(size.ws_row<3){return;} // Too small, would result in negative numbers breaking the chat window
   resize_term(size.ws_row, size.ws_col);
   wresize(topic, 1, COLS);
-  wresize(chat, LINES-3, COLS);
+  wresize(chat, chat->_maxy+1, COLS);
   wresize(input, 2, COLS);
   mvwin(input, LINES-2, 0);
   redrawwin(chat);
   redrawwin(topic);
   redrawwin(input);
-  wrefresh(chat);
+  drawchat();
   wrefresh(topic);
-//  wrefresh(input);
   drawinput();
 }
 
@@ -182,19 +190,20 @@ int main(int argc, char** argv)
   cbreak();
   noecho();
   keypad(w, 1);
+  use_default_colors();
   topic=newwin(1, COLS, 0, 0);
   init_pair(1, COLOR_WHITE, COLOR_BLUE);
 
   // Define colors mapped to ANSI color codes (at least the ones tc_client uses)
-  init_pair(2, COLOR_RED, 0);
-  init_pair(3, COLOR_GREEN, 0);
-  init_pair(4, COLOR_YELLOW, 0);
-  init_pair(5, COLOR_BLUE, 0);
-  init_pair(6, COLOR_MAGENTA, 0);
-  init_pair(7, COLOR_CYAN, 0);
+  init_pair(2, COLOR_RED, -1);
+  init_pair(3, COLOR_GREEN, -1);
+  init_pair(4, COLOR_YELLOW, -1);
+  init_pair(5, COLOR_BLUE, -1);
+  init_pair(6, COLOR_MAGENTA, -1);
+  init_pair(7, COLOR_CYAN, -1);
 
   wbkgd(topic, COLOR_PAIR(1)|' ');
-  chat=newwin(LINES-3, COLS, 1, 0);
+  chat=newpad(2048, COLS);
   scrollok(chat, 1);
   input=newwin(2, COLS, LINES-2, 0);
   scrollok(input, 1);
@@ -247,7 +256,7 @@ int main(int argc, char** argv)
       }
       waddstr(chat, "\n");
       waddansi(chat, buf);
-      wrefresh(chat);
+      drawchat();
       wrefresh(input);
       continue;
     }
@@ -255,22 +264,6 @@ int main(int argc, char** argv)
     p[0].revents=0;
     rl_callback_read_char();
     drawinput();
-#if 0
-// TODO: move this into a function and also call it when the terminal is resized
-    werase(input);
-    unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
-
-    waddstr(input, "> ");
-    int cursor_row=(pos+2)/COLS;
-    int end_row=(rl_end+2)/COLS;
-    // Figure out how much of the buffer to print to not scroll past the cursor
-//    unsigned int eol=cursor_row;// (pos+2)/COLS;
-    unsigned int eol=charstobytes(rl_line_buffer, rl_end, (cursor_row+2)*COLS-3); // -2 for cursor, -1 to avoid wrapping
-    waddnstr(input, rl_line_buffer, eol);
-
-    wmove(input, cursor_row==end_row && cursor_row>0, (pos+2)%COLS); // +2 for prompt
-    wrefresh(input);
-#endif
   }
   rl_callback_handler_remove();
   endwin();
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index 5967088..1041544 100644
--- a/utilities/modbot/modbot.c
+++ b/utilities/modbot/modbot.c
@@ -403,7 +403,7 @@ int main(int argc, char** argv)
                 ++listed;
               }
             }
-            say(pm, "%u videos in queue, %u of which are not yet approved by mods (%s%s)\n", queue.itemcount, notapproved, buf, (listed<notapproved)?", etc.":"");
+            say(pm, "%u video%s in queue, %u of which are not yet approved by mods (%s%s)\n", queue.itemcount, (queue.itemcount==1)?"":"s", notapproved, buf, (listed<notapproved)?", etc.":"");
           }else{
             say(pm, "%u videos in queue\n", queue.itemcount);
           }
@@ -565,6 +565,8 @@ int main(int argc, char** argv)
             list_save(&goodvids, "goodvids.txt");
             free(playing);
             playing=strdup(vid);
+            free(requester);
+            requester=strdup(nick);
             unsigned int pos=(end?(strtol(&end[1], 0, 0)/1000):0);
             alarm(getduration(playing)-pos);
             started=time(0)-pos;