$ git clone http://tcclient.ion.nu/tc_client.git
commit 36d7d412972594436f075b189cbd87f8a1acda83
Author: Alicia <...>
Date:   Mon Dec 12 12:55:33 2016 +0100

    Basic support for greenroom channels.

diff --git a/ChangeLog b/ChangeLog
index c1656b8..9ea198b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,7 @@ Fixes for compiling in a separate directory.
 Buildsystem fixes.
 More fixes for compiling in a separate directory.
 Notify if the captcha was not completed and tinychat puts our session in "lurker" mode.
+Basic support for greenroom channels.
 tc_client-gtk: bugfix: don't rely on stack allocated variables for GUI callbacks.
 tc_client-gtk: install the camera placeholder animation for the 'install' target.
 tc_client-gtk: mark outgoing video keyframes as keyframes.
diff --git a/README b/README
index 0b35e0b..c6e60b4 100644
--- a/README
+++ b/README
@@ -20,14 +20,14 @@ Commands supported by tc_client:
 /audio <length> = send a <length> bytes long encoded frame, send the frame data after this line
 /camdown        = close the audio/video stream
 /topic <topic>  = set the channel topic
-/disablesnapshots = disable flash client's snapshots of our stream.
-/enablesnapshots = re-enable flash client's snapshots of our stream.
+/disablesnapshots = disable flash client's snapshots of our stream
+/enablesnapshots = re-enable flash client's snapshots of our stream
+/allow <nick>   = allow user to broadcast (as a mod)
+/getnick <ID>   = get nickname from connection ID
 /help           = list these commands at runtime
 
 Missing features:
 broadcasting support for channels which require a password to broadcast
-broadcasting support for channels where mods need to approve streams
-support for reviewing and approving streams as a mod
 
 Current commands sent by the TC servers that tc_client doesn't know how to handle:
 notice (some, notice is used for many functions)
diff --git a/client.c b/client.c
index 1aaa609..a6ddb75 100644
--- a/client.c
+++ b/client.c
@@ -37,6 +37,9 @@
 #include "amfwriter.h"
 #include "utilities/compat.h"
 
+const char* sitearg="tinychat";
+const char* bpassword=0;
+
 struct writebuf
 {
   char* buf;
@@ -91,22 +94,23 @@ char* gethost(char *channel, char *password)
   int urllen;
   if(password)
   {
-    urllen=strlen("http://apl.tinychat.com/api/find.room/?site=tinychat&password=0")+strlen(channel)+strlen(password);
+    urllen=strlen("http://apl.tinychat.com/api/find.room/?site=&password=0")+strlen(channel)+strlen(sitearg)+strlen(password);
   }else{
-    urllen=strlen("http://apl.tinychat.com/api/find.room/?site=tinychat0")+strlen(channel);
+    urllen=strlen("http://apl.tinychat.com/api/find.room/?site=0")+strlen(channel)+strlen(sitearg);
   }
   char url[urllen];
   if(password)
   {
-    sprintf(url, "http://apl.tinychat.com/api/find.room/%s?site=tinychat&password=%s", channel, password);
+    sprintf(url, "http://apl.tinychat.com/api/find.room/%s?site=%s&password=%s", channel, sitearg, password);
   }else{
-    sprintf(url, "http://apl.tinychat.com/api/find.room/%s?site=tinychat", channel);
+    sprintf(url, "http://apl.tinychat.com/api/find.room/%s?site=%s", channel, sitearg);
   }
   char* response=http_get(url, 0);
   if(!response){exit(-1);}
   //response contains result='(OK|RES)|PW' (latter means a password is required)
   char* result=strstr(response, "result='");
   if(!result){printf("No result\n"); exit(-1);}
+  char* bpass=strstr(response, " bpassword='");
   result+=strlen("result='");
   // Handle the result value
   if(!strncmp(result, "PW", 2)){printf("Password required\n"); exit(-1);}
@@ -118,14 +122,26 @@ char* gethost(char *channel, char *password)
   int len;
   for(len=0; rtmp[len] && rtmp[len]!='/'; ++len);
   char* host=strndup(rtmp, len);
+  // Check if this is a greenroom channel
+  if(strstr(response, "greenroom=\"1\""))
+  {
+    printf("Channel has greenroom\n");
+    fflush(stdout);
+  }
+  char* end;
+  if(bpass && (end=strchr(&bpass[12], '\'')))
+  {
+    end[0]=0;
+    bpassword=strdup(&bpass[12]);
+  }
   free(response);
   return host;
 }
 
 char* getkey(int id, const char* channel)
 {
-  char url[snprintf(0,0, "http://apl.tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=tinychat%%5E%s", id, channel)+1];
-  sprintf(url, "http://apl.tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=tinychat%%5E%s", id, channel);
+  char url[snprintf(0,0, "http://apl.tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=%s%%5E%s", id, sitearg, channel)+1];
+  sprintf(url, "http://apl.tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=%s%%5E%s", id, sitearg, channel);
   char* response=http_get(url, 0);
   char* key=strstr(response, "\"key\":\"");
 
@@ -165,18 +181,19 @@ char* getcookie(const char* channel)
   return cookie;
 }
 
-char* getbroadcastkey(const char* channel, const char* nick)
+char* getbroadcastkey(const char* channel, const char* nick, const char* bpassword)
 {
   unsigned int id=idlist_get(nick);
-  char url[strlen("http://apl.tinychat.com/api/broadcast.pw?name=&site=tinychat&nick=&id=0")+128+strlen(channel)+strlen(nick)];
-  sprintf(url, "http://apl.tinychat.com/api/broadcast.pw?name=%s&site=tinychat&nick=%s&id=%u", channel, nick, id);
+  char url[snprintf(0,0, "http://apl.tinychat.com/api/broadcast.pw?name=%s&site=%s&nick=%s&id=%u%s%s", channel, sitearg, nick, id, bpassword?"&password=":"", bpassword?bpassword:"")+1];
+  sprintf(url, "http://apl.tinychat.com/api/broadcast.pw?name=%s&site=%s&nick=%s&id=%u%s%s", channel, sitearg, nick, id, bpassword?"&password=":"", bpassword?bpassword:"");
   char* response=http_get(url, 0);
+  if(strstr(response, " result='PW'")){free(response); return 0;}
   char* key=strstr(response, " token='");
 
-  if(!key){return 0;}
+  if(!key){free(response); return 0;}
   key+=8;
   char* keyend=strchr(key, '\'');
-  if(!keyend){return 0;}
+  if(!keyend){free(response); return 0;}
   key=strndup(key, keyend-key);
   free(response);
   return key;
@@ -325,6 +342,7 @@ int main(int argc, char** argv)
     else if(!strcmp(argv[i], "--rtmplog")){++i; rtmplog=open(argv[i], O_WRONLY|O_CREAT|O_TRUNC, 0600); if(rtmplog<0){perror("rtmplog: open");}}
 #endif
     else if(!strcmp(argv[i], "--hexcolors")){hexcolors=1;}
+    else if(!strcmp(argv[i], "--greenroom")){sitearg="greenroom";}
     else if(!channel){channel=argv[i];}
     else if(!nickname){nickname=strdup(argv[i]);}
     else if(!password){password=argv[i];}
@@ -357,6 +375,9 @@ int main(int argc, char** argv)
       if(account_pass[i]=='\n'||account_pass[i]=='\r'){account_pass[i]=0; break;}
     }
   }
+  char loggedin=0;
+  // Log in if user account is specified
+  char* modkey=getmodkey(account_user, account_pass, channel, &loggedin);
   char* server=gethost(channel, password);
   struct addrinfo* res;
   // Separate IP/domain and port
@@ -393,9 +414,6 @@ int main(int argc, char** argv)
   b_read(sock, handshake, 1536); // Read our junk back, we don't bother checking that it's the same
   printf("Handshake complete\n");
   struct rtmp rtmp={0,0,0,0,0};
-  // Handshake complete, log in (if user account is specified)
-  char loggedin=0;
-  char* modkey=getmodkey(account_user, account_pass, channel, &loggedin);
   if(!loggedin){free(account_pass); account_user=0; account_pass=0;}
   getcaptcha();
   char* cookie=getcookie(channel);
@@ -453,7 +471,7 @@ int main(int argc, char** argv)
     amfstring(&amf, account_user?account_user:"");
 
     amfobjitem(&amf, "prefix");
-    amfstring(&amf, "tinychat");
+    amfstring(&amf, sitearg);
 
     amfobjitem(&amf, "room");
     amfstring(&amf, channel);
@@ -525,8 +543,10 @@ int main(int argc, char** argv)
                  "/audio <length> = send a <length> bytes long encoded frame, send the frame data after this line\n"
                  "/topic <topic>  = set the channel topic\n"
                  "/whois <nick/ID> = check a user's username\n"
-                 "/disablesnapshots = disable flash client's snapshots of our stream.\n"
-                 "/enablesnapshots = re-enable flash client's snapshots of our stream.\n");
+                 "/disablesnapshots = disable flash client's snapshots of our stream\n"
+                 "/enablesnapshots = re-enable flash client's snapshots of our stream\n"
+                 "/allow <nick>   = allow user to broadcast\n"
+                 "/getnick <ID>   = get nickname from connection ID\n");
           fflush(stdout);
           continue;
         }
@@ -679,7 +699,7 @@ int main(int argc, char** argv)
         else if(!strcmp(buf, "/camup"))
         {
           // Retrieve and send the key for broadcasting access
-          char* key=getbroadcastkey(channel, nickname);
+          char* key=getbroadcastkey(channel, nickname, bpassword);
           amfinit(&amf, 3);
           amfstring(&amf, "bauth");
           amfnum(&amf, 0);
@@ -740,6 +760,20 @@ int main(int argc, char** argv)
           setallowsnapshots(sock, buf[1]=='e'); // True for "/enablesnapshots", false for "/disablesnapshots"
           continue;
         }
+        else if(!strncmp(buf, "/allow ", 7))
+        {
+          if(!bpassword){continue;}
+          privfield=getprivfield(&buf[7]);
+          if(!privfield){continue;}
+          len=sprintf(buf, "/allowbroadcast %s", bpassword);
+        }
+        else if(!strncmp(buf, "/getnick ", 9))
+        {
+          int id=atoi(&buf[9]);
+          const char* nick=idlist_getnick(id);
+          if(nick){printf("Nickname of connection %i: %s\n", id, nick); fflush(stdout);}
+          continue;
+        }
       }
       char* msg=tonumlist(buf, len);
       amfinit(&amf, 3);
@@ -806,19 +840,15 @@ int main(int argc, char** argv)
         line=nextline;
       }
       char* response=0;
-      if(len==18 && !strncmp(msg, "/userinfo $request", 18))
+      if(!strncmp(msg, "/allowbroadcast ", 16))
       {
-        if(account_user)
+        if(getbroadcastkey(channel, nickname, &msg[16])) // Validate password
         {
-          unsigned int len=strlen("/userinfo ")+strlen(account_user);
-          char buf[len+1];
-          sprintf(buf, "/userinfo %s", account_user);
-          response=tonumlist(buf, len);
-        }else{
-          response=tonumlist("/userinfo $noinfo", 17);
+          free((void*)bpassword);
+          bpassword=strdup(&msg[16]);
         }
       }
-      else if(len==8 && !strncmp(msg, "/version", 8))
+      else if(!strcmp(msg, "/version"))
       {
         response=tonumlist("/version tc_client-" VERSION, strlen(VERSION)+19);
       }
diff --git a/idlist.c b/idlist.c
index 01a9901..aeec5c9 100644
--- a/idlist.c
+++ b/idlist.c
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014-2015  alicia@ion.nu
+    Copyright (C) 2014-2016  alicia@ion.nu
     Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
@@ -104,3 +104,16 @@ char idlist_is_op(const char* name)
   }
   return 0;
 }
+
+const char* idlist_getnick(int id)
+{
+  int i;
+  for(i=0; i<idlistlen; ++i)
+  {
+    if(idlist[i].id==id)
+    {
+      return idlist[i].name;
+    }
+  }
+  return 0;
+}
diff --git a/idlist.h b/idlist.h
index 27ef08e..bbaae31 100644
--- a/idlist.h
+++ b/idlist.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014-2015  alicia@ion.nu
+    Copyright (C) 2014-2016  alicia@ion.nu
     Copyright (C) 2014-2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
@@ -32,3 +32,4 @@ extern void idlist_rename(const char* oldname, 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);
+extern const char* idlist_getnick(int id);