$ git clone https://tcclient.ion.nu/tc_client.git
commit e3341ec8dac53a1f40ee23ace50e615c069db9c2
Author: Alicia <...>
Date:   Thu Jun 1 06:10:40 2017 +0000

    Added some basic support for tinychat beta (-s/--site tinychat_beta)

diff --git a/ChangeLog b/ChangeLog
index 770d904..b2774a5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -15,6 +15,7 @@ Added a /quit command.
 Workaround for the new captcha mechanism.
 Bugfix: use //IGNORE to skip characters instead of freezing if the locale can't represent them.
 Use curl's curl_easy_unescape() for from_owner notices instead of doing it ourselves (contributed by Aida)
+Added some basic support for tinychat beta (-s/--site tinychat_beta)
 modbot: use https instead of http and use the tcclient subdomain since some DNSes have trouble with underscores.
 modbot: added an option (--no-unapproved) to not add any unapproved videos to queue (videos still get approved by mods requesting or playing them manually)
 tc_client-gtk: fixed a race-condition in the builtin video player.
diff --git a/Makefile b/Makefile
index 006b83c..3f84126 100644
--- a/Makefile
+++ b/Makefile
@@ -113,6 +113,14 @@ ifdef READLINE_LIBS
   INSTALLDEPS+=cursedchat
 endif
 endif
+ifdef JSONC_LIBS
+ifdef WEBSOCKET_LIBS
+  CFLAGS+=-DHAVE_WEBSOCKET=1 $(WEBSOCKET_CFLAGS) $(JSONC_CFLAGS)
+  CONFINFO+=|Will enable support for tinychat beta
+  LIBS+=$(WEBSOCKET_LIBS) $(JSONC_LIBS)
+  OBJ+=tinychat_beta.o
+endif
+endif
 ifeq ($(AR),)
   AR=ar
 endif
@@ -160,7 +168,7 @@ libcamera.a: $(LIBCAMERA_OBJ)
 clean:
  rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) $(CURSEDCHAT_OBJ) $(TC_CLIENT_GTK_OBJ) $(LIBCAMERA_OBJ) tc_client irchack modbot camviewer cursedchat tc_client-gtk camplaceholder.gif
 
-SOURCES=Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endianutils.c media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endianutils.h media.h LICENSE README ChangeLog crossbuild.sh testbuilds.sh configure
+SOURCES=Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endianutils.c media.c client.h amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endianutils.h media.h LICENSE README ChangeLog crossbuild.sh testbuilds.sh configure tinychat.c tinychat_beta.c kageshi.c
 SOURCES+=utilities/irchack/irchack.c
 SOURCES+=utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/queue.h utilities/modbot/commands.html
 SOURCES+=utilities/camviewer/camviewer.c
diff --git a/client.c b/client.c
index d3830b1..1935a47 100644
--- a/client.c
+++ b/client.c
@@ -149,6 +149,9 @@ extern int rtmplog;
 #endif
 
 extern int init_tinychat(const char* chanpass, const char* username, const char* userpass, struct site* site);
+#ifdef HAVE_WEBSOCKET
+extern int init_tinychat_beta(const char* chanpass, const char* username, const char* userpass, struct site* site);
+#endif
 extern int init_kageshi(const char* chanpass, const char* username, const char* userpass, struct site* site);
 extern int init_kageshicam(struct site* site);
 int main(int argc, char** argv)
@@ -204,11 +207,15 @@ int main(int argc, char** argv)
   if(!channel||!nickname){usage(argv[0]); return 1;}
   if(sitestr &&
      strcmp(sitestr, "tinychat") &&
+#ifdef HAVE_WEBSOCKET
+     strcmp(sitestr, "tinychat_beta") &&
+#endif
      strcmp(sitestr, "kageshi") &&
      strcmp(sitestr, "kageshicam"))
   {
     printf("Unknown site '%s'. Currently supported sites:\n"
            "tinychat\n"
+           "tinychat_beta (if built with libwebsocket and json-c)\n"
            "kageshi\n"
            "kageshicam (server/camname/numkey as channel)\n", sitestr);
     return 1;
@@ -240,6 +247,13 @@ int main(int argc, char** argv)
   {
     domain=&domain[3];
     if(!strncmp(domain, "www.", 4)){domain=&domain[4];}
+#ifdef HAVE_WEBSOCKET
+    if(!strncmp(domain, "tinychat.com/room/", 18))
+    {
+      sitestr="tinychat_beta";
+      channel=&domain[18];
+    }else
+#endif
     if(!strncmp(domain, "tinychat.com/", 13))
     {
       sitestr="tinychat";
@@ -253,12 +267,18 @@ int main(int argc, char** argv)
     char* slash=strchr(channel, '/');
     if(slash){slash[0]=0;}
   }
-  struct site site;
+  struct site site={0};
   int sock;
   if(!sitestr || !strcmp(sitestr, "tinychat"))
   {
     sock=init_tinychat(password, account_user, account_pass, &site);
   }
+#ifdef HAVE_WEBSOCKET
+  else if(!sitestr || !strcmp(sitestr, "tinychat_beta"))
+  {
+    sock=init_tinychat_beta(password, account_user, account_pass, &site);
+  }
+#endif
   else if(!strcmp(sitestr, "kageshi"))
   {
     sock=init_kageshi(password, account_user, account_pass, &site);
@@ -283,6 +303,11 @@ int main(int argc, char** argv)
   pfd[1].events=POLLIN;
   pfd[1].revents=0;
   struct rtmp rtmp={0,0,0,0,0};
+  conn c;
+#ifdef HAVE_WEBSOCKET
+  if(site.websocket){c.ws=site.websocket;}else
+#endif
+  {c.fd=sock;}
   while(1)
   {
     // Poll for input, very crude chat UI
@@ -362,7 +387,7 @@ int main(int argc, char** argv)
         }
         else if(!strncmp(buf, "/nick ", 6))
         {
-          site.nick(sock, &buf[6]);
+          site.nick(c, &buf[6]);
           continue;
         }
         else if(!strncmp(buf, "/msg ", 5))
@@ -371,38 +396,38 @@ int main(int argc, char** argv)
           if(msg)
           {
             msg[0]=0;
-            site.sendpm(sock, &msg[1], &buf[5]);
+            site.sendpm(c, &msg[1], &buf[5]);
             continue;
           }
         }
         else if(!strncmp(buf, "/opencam ", 9))
         {
-          site.opencam(sock, &buf[9]);
+          site.opencam(c, &buf[9]);
           continue;
         }
         else if(!strncmp(buf, "/closecam ", 10))
         {
-          site.closecam(sock, &buf[10]);
+          site.closecam(c, &buf[10]);
           continue;
         }
         else if(!strncmp(buf, "/close ", 7)) // Stop someone's cam/mic broadcast
         {
-          site.mod_close(sock, &buf[7]);
+          site.mod_close(c, &buf[7]);
           continue;
         }
         else if(!strncmp(buf, "/ban ", 5)) // Ban someone
         {
-          site.mod_ban(sock, &buf[5]);
+          site.mod_ban(c, &buf[5]);
           continue;
         }
         else if(!strcmp(buf, "/banlist"))
         {
-          site.mod_banlist(sock);
+          site.mod_banlist(c);
           continue;
         }
         else if(!strncmp(buf, "/forgive ", 9))
         {
-          site.mod_unban(sock, &buf[9]);
+          site.mod_unban(c, &buf[9]);
           continue;
         }
         else if(!strcmp(buf, "/names"))
@@ -418,22 +443,22 @@ int main(int argc, char** argv)
         }
         else if(!strcmp(buf, "/mute"))
         {
-          site.mod_mute(sock);
+          site.mod_mute(c);
           continue;
         }
         else if(!strcmp(buf, "/push2talk"))
         {
-          site.mod_push2talk(sock);
+          site.mod_push2talk(c);
           continue;
         }
         else if(!strcmp(buf, "/camup"))
         {
-          site.camup(sock);
+          site.camup(c);
           continue;
         }
         else if(!strcmp(buf, "/camdown"))
         {
-          site.camdown(sock);
+          site.camdown(c);
           continue;
         }
         else if(!strncmp(buf, "/video ", 7)) // Send video data
@@ -454,7 +479,7 @@ int main(int argc, char** argv)
         }
         else if(!strncmp(buf, "/topic ", 7))
         {
-          site.mod_topic(sock, &buf[7]);
+          site.mod_topic(c, &buf[7]);
           continue;
         }
         else if(!strncmp(buf, "/whois ", 7)) // Get account username
@@ -476,7 +501,7 @@ int main(int argc, char** argv)
         }
         else if(!strncmp(buf, "/allow ", 7))
         {
-          site.mod_allowbroadcast(sock, &buf[7]);
+          site.mod_allowbroadcast(c, &buf[7]);
           continue;
         }
         else if(!strncmp(buf, "/getnick ", 9))
@@ -488,29 +513,44 @@ int main(int argc, char** argv)
         }
         else if(!strcmp(buf, "/quit")){break;}
       }
-      site.sendmessage(sock, buf);
-      continue;
+      site.sendmessage(c, buf);
     }
-    // Got data from server
-    pfd[1].revents=0;
-    // Read the RTMP stream and handle AMF0 packets
-    char rtmpres=rtmp_get(sock, &rtmp);
-    if(!rtmpres){printf("Server disconnected\n"); break;}
-    if(rtmpres==2){continue;} // Not disconnected, but we didn't get a complete chunk yet either
-    if(rtmp.type==RTMP_VIDEO || rtmp.type==RTMP_AUDIO){stream_handledata(&rtmp); continue;}
-    if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); fflush(stdout); continue;}
-    struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
-    for(i=0; i<commandcount; ++i)
+    if(pfd[1].revents)
     {
-      if(amfin->itemcount>=commands[i].minargs && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, commands[i].command))
+      // Got data from server
+      pfd[1].revents=0;
+#ifdef HAVE_WEBSOCKET
+      if(site.websocket)
       {
-        commands[i].callback(amfin, sock);
+        struct websock_head head;
+        if(!websock_readhead(c.ws, &head)){printf("Server disconnected\n"); break;}
+        char buf[head.length];
+        websock_readcontent(c.ws, buf, &head);
+        site.handlewspacket(c.ws, buf, &head);
         fflush(stdout);
-        break;
+      }else
+#endif
+      {
+        // Read the RTMP stream and handle AMF0 packets
+        char rtmpres=rtmp_get(sock, &rtmp);
+        if(!rtmpres){printf("Server disconnected\n"); break;}
+        if(rtmpres==2){continue;} // Not disconnected, but we didn't get a complete chunk yet either
+        if(rtmp.type==RTMP_VIDEO || rtmp.type==RTMP_AUDIO){stream_handledata(&rtmp); continue;}
+        if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); fflush(stdout); continue;}
+        struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
+        for(i=0; i<commandcount; ++i)
+        {
+          if(amfin->itemcount>=commands[i].minargs && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, commands[i].command))
+          {
+            commands[i].callback(amfin, sock);
+            fflush(stdout);
+            break;
+          }
+        }
+        // if(i==commandcount){printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
+        amf_free(amfin);
       }
     }
-    // if(i==commandcount){printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
-    amf_free(amfin);
   }
   free(rtmp.buf);
   curl_easy_cleanup(curl);
diff --git a/client.h b/client.h
index 1c3f312..1139e46 100644
--- a/client.h
+++ b/client.h
@@ -1,21 +1,35 @@
+#ifdef HAVE_WEBSOCKET
+#include <libwebsocket/websock.h>
+#endif
 #include "amfparser.h"
+typedef union
+{
+  int fd;
+#ifdef HAVE_WEBSOCKET
+  websock_conn* ws;
+#endif
+} conn;
 struct site
 {
-  void(*sendmessage)(int sock, const char* buf);
-  void(*sendpm)(int sock, const char* buf, const char* recipient);
-  void(*nick)(int sock, const char* newnick);
-  void(*mod_close)(int sock, const char* nick);
-  void(*mod_ban)(int sock, const char* nick);
-  void(*mod_banlist)(int sock);
-  void(*mod_unban)(int sock, const char* nick);
-  void(*mod_mute)(int sock);
-  void(*mod_push2talk)(int sock);
-  void(*mod_topic)(int sock, const char* newtopic);
-  void(*mod_allowbroadcast)(int sock, const char* nick);
-  void(*opencam)(int sock, const char* arg);
-  void(*closecam)(int sock, const char* arg);
-  void(*camup)(int sock);
-  void(*camdown)(int sock);
+  void(*sendmessage)(conn c, const char* buf);
+  void(*sendpm)(conn c, const char* buf, const char* recipient);
+  void(*nick)(conn c, const char* newnick);
+  void(*mod_close)(conn c, const char* nick);
+  void(*mod_ban)(conn c, const char* nick);
+  void(*mod_banlist)(conn c);
+  void(*mod_unban)(conn c, const char* nick);
+  void(*mod_mute)(conn c);
+  void(*mod_push2talk)(conn c);
+  void(*mod_topic)(conn c, const char* newtopic);
+  void(*mod_allowbroadcast)(conn c, const char* nick);
+  void(*opencam)(conn c, const char* arg);
+  void(*closecam)(conn c, const char* arg);
+  void(*camup)(conn c);
+  void(*camdown)(conn c);
+#ifdef HAVE_WEBSOCKET
+  websock_conn* websocket;
+  void(*handlewspacket)(websock_conn*, void*, struct websock_head*);
+#endif
 };
 
 extern char greenroom;
diff --git a/configure b/configure
index c3cf343..167a9a9 100755
--- a/configure
+++ b/configure
@@ -255,6 +255,9 @@ fi
 testpkgconfig webkit2gtk-4.0 WEBKITGTK || \
 testpkgconfig webkit2gtk-3.0 WEBKITGTK
 
+testpkgconfig libwebsocket WEBSOCKET
+testpkgconfig json-c JSONC
+
 printf 'Checking if we have a working poll()... '
 echo '#include <poll.h>' > polltest.c
 echo '#include <sys/socket.h>' >> polltest.c
diff --git a/idlist.c b/idlist.c
index aeec5c9..d5f6a00 100644
--- a/idlist.c
+++ b/idlist.c
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014-2016  alicia@ion.nu
+    Copyright (C) 2014-2017  alicia@ion.nu
     Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
@@ -48,6 +48,21 @@ void idlist_remove(const char* name)
   }
 }
 
+void idlist_removeid(int id)
+{
+  int i;
+  for(i=0; i<idlistlen; ++i)
+  {
+    if(idlist[i].id==id)
+    {
+      free((void*)idlist[i].name);
+      --idlistlen;
+      memmove(&idlist[i], &idlist[i+1], sizeof(struct idmap)*(idlistlen-i));
+      return;
+    }
+  }
+}
+
 void idlist_rename(const char* oldname, const char* newname)
 {
   int i;
@@ -62,6 +77,20 @@ void idlist_rename(const char* oldname, const char* newname)
   }
 }
 
+void idlist_renameid(int id, const char* newname)
+{
+  int i;
+  for(i=0; i<idlistlen; ++i)
+  {
+    if(idlist[i].id==id)
+    {
+      free((void*)idlist[i].name);
+      idlist[i].name=strdup(newname);
+      return;
+    }
+  }
+}
+
 int idlist_get(const char* name)
 {
   int len;
diff --git a/idlist.h b/idlist.h
index bbaae31..108ab40 100644
--- a/idlist.h
+++ b/idlist.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014-2016  alicia@ion.nu
+    Copyright (C) 2014-2017  alicia@ion.nu
     Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
@@ -28,7 +28,9 @@ extern int idlistlen;
 
 extern void idlist_add(int id, const char* name, const char* account, char op);
 extern void idlist_remove(const char* name);
+extern void idlist_removeid(int id);
 extern void idlist_rename(const char* oldname, const char* newname);
+extern void idlist_renameid(int id, const char* newname);
 extern int idlist_get(const char* name);
 extern const char* idlist_getaccount(const char* name);
 extern char idlist_is_op(const char* name);
diff --git a/kageshi.c b/kageshi.c
index 37f1f6f..6da9238 100644
--- a/kageshi.c
+++ b/kageshi.c
@@ -162,7 +162,7 @@ static void result2(struct amf* amfin, int sock)
   }
 }
 
-static void sendmessage(int sock, const char* buf)
+static void sendmessage(conn c, const char* buf)
 {
   char color[8];
   memcpy(color, colors[currentcolor%COLORCOUNT], 7);
@@ -184,11 +184,11 @@ static void sendmessage(int sock, const char* buf)
   amfstring(&amf, color);
   amfstring(&amf, "1");
   amfnum(&amf, 1);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
 /* Not used yet
-static void sendpmtyping(int sock, const char* nick)
+static void sendpmtyping(conn c, const char* nick)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
@@ -200,11 +200,11 @@ static void sendpmtyping(int sock, const char* nick)
   amfstring(&amf, nickname); // Spoofable?
   amfstring(&amf, nick);
   amfstring(&amf, "1"); // 0 for not typing anymore? (or 2? from observations)
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 */
 
-static void sendpm(int sock, const char* buf, const char* nick)
+static void sendpm(conn c, const char* buf, const char* nick)
 {
   char color[8];
   memcpy(color, colors[currentcolor%COLORCOUNT], 7);
@@ -226,7 +226,7 @@ static void sendpm(int sock, const char* buf, const char* nick)
   amfstring(&amf, color);
   amfstring(&amf, "1");
   amfnum(&amf, 1);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
 static void notimplemented()
@@ -370,14 +370,14 @@ static void result(struct amf* amfin, int sock)
   }
 }
 
-static void opencam(int sock, const char* camname)
+static void opencam(conn c, const char* camname)
 {
-  stream_start(camname, camname, sock);
+  stream_start(camname, camname, c.fd);
 }
 
-static void closecam(int sock, const char* camname)
+static void closecam(conn c, const char* camname)
 {
-  stream_stopvideo(sock, camname);
+  stream_stopvideo(c.fd, camname);
 }
 
 static void notimplemented2()
diff --git a/tinychat.c b/tinychat.c
index 884ae0e..a239a20 100644
--- a/tinychat.c
+++ b/tinychat.c
@@ -204,7 +204,7 @@ static char checknick(const char* nick) // Returns zero if the nick is valid, ot
   return 0;
 }
 
-static void sendmessage_priv(int sock, const char* buf, const char* priv)
+static void sendmessage_priv(conn c, const char* buf, const char* priv)
 {
   int id=priv?idlist_get(priv):0;
   char privfield[snprintf(0, 0, "n%i-%s", id, priv?priv:"")+1];
@@ -232,13 +232,13 @@ static void sendmessage_priv(int sock, const char* buf, const char* priv)
     amfstring(&bamf, msg);
     amfstring(&bamf, colors[currentcolor%COLORCOUNT]);
     amfstring(&bamf, privfield);
-    amfsend(&bamf, sock);
+    amfsend(&bamf, c.fd);
   }
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
   free(msg);
 }
 
-static void sendmessage(int sock, const char* buf)
+static void sendmessage(conn c, const char* buf)
 {
   if(!strncmp(buf, "/priv ", 6))
   {
@@ -248,21 +248,21 @@ static void sendmessage(int sock, const char* buf)
       char priv[msg-&buf[6]+1];
       memcpy(priv, &buf[6], msg-&buf[6]);
       priv[msg-&buf[6]]=0;
-      sendmessage_priv(sock, &msg[1], priv);
+      sendmessage_priv(c, &msg[1], priv);
       return;
     }
   }
-  sendmessage_priv(sock, buf, 0);
+  sendmessage_priv(c, buf, 0);
 }
 
-static void sendpm(int sock, const char* buf, const char* recipient)
+static void sendpm(conn c, const char* buf, const char* recipient)
 {
   char msg[strlen("/msg  0")+strlen(recipient)+strlen(buf)];
   sprintf(msg, "/msg %s %s", recipient, buf);
-  sendmessage_priv(sock, msg, recipient);
+  sendmessage_priv(c, msg, recipient);
 }
 
-static void changenick(int sock, const char* newnick)
+static void changenick(conn c, const char* newnick)
 {
   char badchar;
   if((badchar=checknick(newnick)))
@@ -276,7 +276,7 @@ static void changenick(int sock, const char* newnick)
   amfnum(&amf, 0);
   amfnull(&amf);
   amfstring(&amf, newnick);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
 static void joinreg(struct amf* amfin, int sock)
@@ -328,7 +328,7 @@ static void joinreg(struct amf* amfin, int sock)
         amfsend(&amf, sock);
         free(key);
         // Set the given nickname
-        changenick(sock, nickname);
+        changenick((conn)sock, nickname);
         // Keep what the server gave us, just in case
         free(nickname);
         nickname=strdup(nick);
@@ -399,7 +399,7 @@ static void privmsg(struct amf* amfin, int sock)
   }
   else if(!strcmp(msg, "/version"))
   {
-    sendmessage_priv(sock, "/version tc_client-" VERSION, amfin->items[5].string.string);
+    sendmessage_priv((conn)sock, "/version tc_client-" VERSION, amfin->items[5].string.string);
   }
   free(msg);
 }
@@ -442,12 +442,12 @@ static void banned(struct amf* amfin, int sock)
   exit(1); // Getting banned is a failure, right?
 }
 
-static void closecam(int sock, const char* nick)
+static void closecam(conn c, const char* nick)
 {
   unsigned int userid=idlist_get(nick);
   char camid[snprintf(0,0,"%u", userid)+1];
   sprintf(camid, "%u", userid);
-  stream_stopvideo(sock, camid);
+  stream_stopvideo(c.fd, camid);
 }
 
 static void fromowner(struct amf* amfin, int sock)
@@ -473,7 +473,7 @@ static void fromowner(struct amf* amfin, int sock)
   else if(!strncmp("_close", amfin->items[2].string.string, 6) && !strcmp(&amfin->items[2].string.string[6], nickname))
   {
     printf("Outgoing media stream was closed\n");
-    closecam(sock, nickname);
+    closecam((conn)sock, nickname);
   }
 }
 
@@ -547,7 +547,7 @@ static void onstatus(struct amf* amfin, int sock)
   stream_handlestatus(amfin, sock);
 }
 
-static void mod_close(int sock, const char* nick)
+static void mod_close(conn c, const char* nick)
 {
   struct rtmp amf;
   amfinit(&amf, 2);
@@ -557,12 +557,12 @@ static void mod_close(int sock, const char* nick)
   char buf[strlen("closed: 0")+strlen(nick)];
   sprintf(buf, "_close%s", nick);
   amfstring(&amf, buf);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
   sprintf(buf, "closed: %s", nick);
-  sendmessage(sock, buf);
+  sendmessage(c, buf);
 }
 
-static void mod_ban(int sock, const char* nick)
+static void mod_ban(conn c, const char* nick)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
@@ -574,7 +574,7 @@ static void mod_ban(int sock, const char* nick)
   char buf[strlen("notice%20was%20banned%20by%200")+strlen(nick)+strlen(nickname)];
   sprintf(buf, "notice%s%%20was%%20banned%%20by%%20%s", nick, nickname);
   amfstring(&amf, buf);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 //  printf("%s %s was banned by %s (%s)\n", timestamp(), nick, nickname, account_user);
   printf("%s %s was banned by %s\n", timestamp(), nick, nickname);
   // kick (this does the actual banning)
@@ -585,27 +585,27 @@ static void mod_ban(int sock, const char* nick)
   amfstring(&amf, nick);
   sprintf(buf, "%i", idlist_get(nick));
   amfstring(&amf, buf);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
-static void mod_banlist(int sock)
+static void mod_banlist(conn c)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
   amfstring(&amf, "banlist");
   amfnum(&amf, 0);
   amfnull(&amf);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
-static void mod_unban(int sock, const char* nick)
+static void mod_unban(conn c, const char* nick)
 {
   free(unban);
   unban=strdup(nick);
-  mod_banlist(sock);
+  mod_banlist(c);
 }
 
-static void mod_mute(int sock)
+static void mod_mute(conn c)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
@@ -613,10 +613,10 @@ static void mod_mute(int sock)
   amfnum(&amf, 0);
   amfnull(&amf);
   amfstring(&amf, "mute");
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
-static void mod_push2talk(int sock)
+static void mod_push2talk(conn c)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
@@ -624,10 +624,10 @@ static void mod_push2talk(int sock)
   amfnum(&amf, 0);
   amfnull(&amf);
   amfstring(&amf, "push2talk");
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
-static void mod_topic(int sock, const char* newtopic)
+static void mod_topic(conn c, const char* newtopic)
 {
   struct rtmp amf;
   amfinit(&amf, 3);
@@ -636,18 +636,18 @@ static void mod_topic(int sock, const char* newtopic)
   amfnull(&amf);
   amfstring(&amf, newtopic);
   amfstring(&amf, "");
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
 }
 
-static void mod_allowbroadcast(int sock, const char* nick)
+static void mod_allowbroadcast(conn c, const char* nick)
 {
   if(!bpassword){return;}
   char buf[strlen("/allowbroadcast 0")+strlen(bpassword)];
   sprintf(buf, "/allowbroadcast %s", bpassword);
-  sendmessage_priv(sock, buf, nick);
+  sendmessage_priv(c, buf, nick);
 }
 
-static void camup(int sock)
+static void camup(conn c)
 {
   // Retrieve and send the key for broadcasting access
   char* key=getbroadcastkey(channel, nickname, bpassword);
@@ -658,29 +658,29 @@ static void camup(int sock)
   amfnum(&amf, 0);
   amfnull(&amf);
   amfstring(&amf, key);
-  amfsend(&amf, sock);
+  amfsend(&amf, c.fd);
   free(key);
   // Initiate stream
   unsigned int userid=idlist_get(nickname);
   char camid[snprintf(0,0,"%u", userid)+1];
   sprintf(camid, "%u", userid);
-  streamout_start(camid, sock);
+  streamout_start(camid, c.fd);
 }
 
-static void camdown(int sock)
+static void camdown(conn c)
 {
   unsigned int userid=idlist_get(nickname);
   char camid[snprintf(0,0,"%u", userid)+1];
   sprintf(camid, "%u", userid);
-  stream_stopvideo(sock, camid);
+  stream_stopvideo(c.fd, camid);
 }
 
-static void opencam(int sock, const char* nick)
+static void opencam(conn c, const char* nick)
 {
   unsigned int userid=idlist_get(nick);
   char camid[snprintf(0,0,"%u", userid)+1];
   sprintf(camid, "%u", userid);
-  stream_start(nick, camid, sock);
+  stream_start(nick, camid, c.fd);
 }
 
 int init_tinychat(const char* chanpass, const char* username, const char* userpass, struct site* site)
diff --git a/tinychat_beta.c b/tinychat_beta.c
new file mode 100644
index 0000000..c9ecd4e
--- /dev/null
+++ b/tinychat_beta.c
@@ -0,0 +1,207 @@
+/*
+    tc_client, a simple non-flash client for tinychat(.com)
+    Copyright (C) 2017  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
+    the Free Software Foundation, version 3 of the License.
+
+    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 Affero General Public License for more details.
+
+    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/>.
+*/
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <json.h>
+#include "client.h"
+#include "idlist.h"
+
+static const char* user_join(struct json_object* user)
+{
+  struct json_object* value;
+  if(!json_object_object_get_ex(user, "nick", &value)){return 0;}
+  const char* nick=json_object_get_string(value);
+  if(!json_object_object_get_ex(user, "username", &value)){return 0;}
+  const char* account=json_object_get_string(value);
+  if(!json_object_object_get_ex(user, "handle", &value)){return 0;}
+  int64_t id=json_object_get_int64(value);
+  if(!json_object_object_get_ex(user, "mod", &value)){return 0;}
+  char mod=json_object_get_boolean(value);
+  idlist_add(id, nick, account, mod);
+  return nick;
+}
+
+static const char* chanpass=0;
+static void handlepacket(websock_conn* conn, void* data, struct websock_head* head)
+{
+  // Parse JSON and handle data
+  struct json_tokener* tok=json_tokener_new();
+  struct json_object* obj=json_tokener_parse_ex(tok, data, head->length);
+  json_tokener_free(tok);
+  if(!obj){return;}
+  struct json_object* cmdobj;
+  if(!json_object_object_get_ex(obj, "tc", &cmdobj)){json_object_put(obj); return;}
+  const char* cmd=json_object_get_string(cmdobj);
+  if(!strcmp(cmd, "ping")){websock_write(conn, "{\"tc\":\"pong\"}", 13, WEBSOCK_TEXT);}
+  else if(!strcmp(cmd, "password"))
+  {
+    if(!chanpass){printf("Password required\n"); exit(-1);}
+    char buf[strlen("{\"tc\":\"password\",\"password\":\"\"}0")+strlen(chanpass)];
+    sprintf(buf, "{\"tc\":\"password\",\"password\":\"%s\"}", chanpass);
+    websock_write(conn, buf, strlen(buf), WEBSOCK_TEXT);
+  }
+  else if(!strcmp(cmd, "userlist"))
+  {
+    struct json_object* users;
+    if(!json_object_object_get_ex(obj, "users", &users)){json_object_put(obj); return;}
+    if(!json_object_is_type(users, json_type_array)){json_object_put(obj); return;}
+    printf("Currently online: ");
+    unsigned int i;
+    for(i=0; i<json_object_array_length(users); ++i)
+    {
+      struct json_object* user=json_object_array_get_idx(users, i);
+      if(!user){continue;}
+      const char* nick=user_join(user);
+      if(!nick){continue;}
+      printf(i?", %s":"%s", nick);
+    }
+    printf("\n");
+    // List everyone who is a mod
+    for(i=0; i<idlistlen; ++i)
+    {
+      if(idlist[i].op){printf("%s is a moderator.\n", idlist[i].name);}
+    }
+  }
+  else if(!strcmp(cmd, "msg"))
+  {
+    if(!json_object_object_get_ex(obj, "text", &cmdobj)){json_object_put(obj); return;}
+    const char* msg=json_object_get_string(cmdobj);
+    if(!json_object_object_get_ex(obj, "handle", &cmdobj)){json_object_put(obj); return;}
+    int64_t handle=json_object_get_int64(cmdobj);
+    const char* nick=idlist_getnick(handle);
+    if(!strcmp(nick, nickname)){json_object_put(obj); return;} // Ignore messages from ourselves (server sends it back along with sending it to everyone else)
+    printf("%s %s: %s\n", timestamp(), nick, msg);
+  }
+  else if(!strcmp(cmd, "joined"))
+  {
+    struct json_object* channel;
+    if(!json_object_object_get_ex(obj, "room", &channel)){json_object_put(obj); return;}
+    if(!json_object_object_get_ex(channel, "topic", &cmdobj)){json_object_put(obj); return;}
+    const char* topic=json_object_get_string(cmdobj);
+    printf("Room topic: %s\n", topic);
+    if(!json_object_object_get_ex(obj, "self", &cmdobj)){json_object_put(obj); return;}
+    const char* nick=user_join(cmdobj);
+    printf("Connection ID: %i\n", idlist[0].id);
+    if(nick)
+    {
+      free(nickname);
+      nickname=strdup(nick);
+    }
+  }
+  else if(!strcmp(cmd, "nick"))
+  {
+    if(!json_object_object_get_ex(obj, "success", &cmdobj)){json_object_put(obj); return;}
+    if(!json_object_get_boolean(cmdobj)){json_object_put(obj); return;} // Ignore failed nick changes
+    if(!json_object_object_get_ex(obj, "nick", &cmdobj)){json_object_put(obj); return;}
+    const char* nick=json_object_get_string(cmdobj);
+    if(!json_object_object_get_ex(obj, "handle", &cmdobj)){json_object_put(obj); return;}
+    int64_t handle=json_object_get_int64(cmdobj);
+    const char* oldnick=idlist_getnick(handle);
+    printf("%s %s changed nickname to %s\n", timestamp(), oldnick, nick);
+    if(!strcmp(oldnick, nickname)){free(nickname); nickname=strdup(nick);}
+    idlist_renameid(handle, nick);
+  }
+  else if(!strcmp(cmd, "join"))
+  {
+    const char* nick=user_join(obj);
+    if(!nick){json_object_put(obj); return;}
+    printf("%s %s entered the channel\n", timestamp(), nick);
+    if(!json_object_object_get_ex(obj, "mod", &cmdobj)){json_object_put(obj); return;}
+    if(json_object_get_boolean(cmdobj))
+    {
+      printf("%s is a moderator.\n", nick);
+    }
+  }
+  else if(!strcmp(cmd, "quit"))
+  {
+    if(!json_object_object_get_ex(obj, "handle", &cmdobj)){json_object_put(obj); return;}
+    int64_t handle=json_object_get_int64(cmdobj);
+    const char* nick=idlist_getnick(handle);
+    printf("%s %s left the channel\n", timestamp(), nick);
+    idlist_removeid(handle);
+  }else{
+    write(1, "Received: ", 10);
+    write(1, data, head->length);
+    write(1, "\n", 1);
+    printf("Command: %s\n", cmd);
+  }
+  json_object_put(obj);
+}
+
+static void sendmessage(conn c, const char* msg)
+{
+  struct json_object* obj=json_object_new_object();
+  json_object_object_add(obj, "tc", json_object_new_string("msg"));
+  json_object_object_add(obj, "text", json_object_new_string(msg));
+  const char* json=json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN);
+  websock_write(c.ws, json, strlen(json), WEBSOCK_TEXT);
+  json_object_put(obj);
+}
+
+static void setnick(conn c, const char* nick)
+{
+  struct json_object* obj=json_object_new_object();
+  json_object_object_add(obj, "tc", json_object_new_string("nick"));
+  json_object_object_add(obj, "nick", json_object_new_string(nick));
+  const char* json=json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN);
+  websock_write(c.ws, json, strlen(json), WEBSOCK_TEXT);
+  json_object_put(obj);
+}
+
+static void notimplemented()
+{
+  printf("The requested feature is either not supported by tinychat beta or not yet implemented in tc_client's tinychat beta support\n");
+}
+
+int init_tinychat_beta(const char* chanpass_, const char* username, const char* userpass, struct site* site)
+{
+  site->handlewspacket=handlepacket;
+  site->sendmessage=sendmessage;
+  site->sendpm=notimplemented;
+  site->nick=setnick;
+  site->mod_close=notimplemented;
+  site->mod_ban=notimplemented;
+  site->mod_banlist=notimplemented;
+  site->mod_unban=notimplemented;
+  site->mod_mute=notimplemented;
+  site->mod_push2talk=notimplemented;
+  site->mod_topic=notimplemented;
+  site->mod_allowbroadcast=notimplemented;
+  site->opencam=notimplemented;
+  site->closecam=notimplemented;
+  site->camup=notimplemented;
+  site->camdown=notimplemented;
+  chanpass=chanpass_;
+  int sock=connectto("wss.tinychat.com", "443");
+  site->websocket=websock_new(sock, 1, 0, 0);
+  if(!websock_handshake_client(site->websocket, "/", "wss.tinychat.com", "tc", "https://tinychat.com", 0)){printf("Websocket handshake failed\n"); return -1;}
+  char url[strlen("https://tinychat.com/api/v1.0/room/token/0")+strlen(channel)];
+  sprintf(url, "https://tinychat.com/api/v1.0/room/token/%s", channel);
+  char* tokenfile=http_get(url, 0);
+  if(strlen(tokenfile)<11){return -1;}
+  char* token=&tokenfile[11];
+  char* end;
+  if((end=strchr(token, '"'))){end[0]=0;}
+  const char* fmt="{\"tc\":\"join\",\"useragent\":\"tc_client\",\"token\":\"%s\",\"room\":\"%s\",\"nick\":\"%s\"}";
+  char buf[snprintf(0,0, fmt, token, channel, nickname)+1];
+  sprintf(buf, fmt, token, channel, nickname);
+  free(tokenfile);
+  websock_write(site->websocket, buf, strlen(buf), WEBSOCK_TEXT);
+  return sock;
+}