$ git clone https://tcclient.ion.nu/tc_client.git
commit b44bfc1b10b95840bc08b2feb6fc12341093566f
Author: Alicia <...>
Date: Thu Dec 29 18:16:31 2016 +0100
Added some basic support for kageshi (using the -s/--site option)
diff --git a/ChangeLog b/ChangeLog
index 4a4583d..c7f68ed 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,6 @@
0.42:
Improved RTMP compatibility by responding to RTMP ping requests and always starting new chunk streams with a format 0 packet.
+Added some basic support for kageshi (using the -s/--site option)
0.41.1:
Use tinychat.com instead of apl.tinychat.com (works around SSL/TLS issues on windows)
0.41:
diff --git a/Makefile b/Makefile
index e9412f6..8e5979f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.41.1
+VERSION=0.42pre
CFLAGS=-g3 -Wall -I. $(shell curl-config --cflags)
LDFLAGS=-g3
PREFIX=/usr/local
@@ -11,7 +11,7 @@ else
@echo 'Run ./configure first, make sure tc_client-gtk is enabled.'
endif
endif
-OBJ=client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endianutils.o media.o utilities/compat.o
+OBJ=client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endianutils.o media.o tinychat.o kageshi.o utilities/compat.o
IRCHACK_OBJ=utilities/irchack/irchack.o utilities/compat.o
MODBOT_OBJ=utilities/modbot/modbot.o utilities/list.o utilities/modbot/queue.o utilities/compat.o
CAMVIEWER_OBJ=utilities/camviewer/camviewer.o utilities/compat.o utilities/compat_av.o libcamera.a
diff --git a/amfparser.h b/amfparser.h
index 7244445..e2313e5 100644
--- a/amfparser.h
+++ b/amfparser.h
@@ -14,6 +14,8 @@
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/>.
*/
+#ifndef AMFPARSER_H
+#define AMFPARSER_H
enum
{
AMF_NUMBER,
@@ -68,3 +70,4 @@ extern void amf_free(struct amf* amf);
extern struct amfitem* amf_getobjmember(struct amfobject* obj, const char* name);
extern void printamf(struct amf* amf);
+#endif
diff --git a/amfwriter.h b/amfwriter.h
index 62f735e..a709306 100644
--- a/amfwriter.h
+++ b/amfwriter.h
@@ -14,6 +14,8 @@
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 <stdlib.h>
+#include "rtmp.h"
extern void amfinit(struct rtmp* msg, unsigned int streamid);
extern void amfnum(struct rtmp* msg, double v);
diff --git a/client.c b/client.c
index e0d574c..cf36e2b 100644
--- a/client.c
+++ b/client.c
@@ -36,9 +36,20 @@
#include "media.h"
#include "amfwriter.h"
#include "utilities/compat.h"
+#include "client.h"
-const char* sitearg="tinychat";
-const char* bpassword=0;
+struct command
+{
+ const char* command;
+ void(*callback)(struct amf*,int);
+ unsigned int minargs;
+};
+static struct command* commands=0;
+static unsigned int commandcount=0;
+
+char greenroom=0;
+char* channel=0;
+char* nickname=0;
struct writebuf
{
@@ -46,17 +57,6 @@ struct writebuf
int len;
};
-void b_read(int sock, void* buf, size_t len)
-{
- while(len>0)
- {
- size_t r=read(sock, buf, len);
- if(r<1){return;}
- len-=r;
- buf+=r;
- }
-}
-
size_t writehttp(char* ptr, size_t size, size_t nmemb, void* x)
{
struct writebuf* data=x;
@@ -68,10 +68,10 @@ size_t writehttp(char* ptr, size_t size, size_t nmemb, void* x)
return size;
}
-CURL* curl=0;
const char* cookiefile="";
char* http_get(const char* url, const char* post)
{
+ static CURL* curl=0;
if(!curl){curl=curl_easy_init();}
if(!curl){return 0;}
curl_easy_setopt(curl, CURLOPT_URL, url);
@@ -89,160 +89,6 @@ char* http_get(const char* url, const char* post)
return writebuf.buf; // should be free()d when no longer needed
}
-char* gethost(char *channel, char *password)
-{
- int urllen;
- if(password)
- {
- urllen=strlen("http://tinychat.com/api/find.room/?site=&password=0")+strlen(channel)+strlen(sitearg)+strlen(password);
- }else{
- urllen=strlen("http://tinychat.com/api/find.room/?site=0")+strlen(channel)+strlen(sitearg);
- }
- char url[urllen];
- if(password)
- {
- sprintf(url, "http://tinychat.com/api/find.room/%s?site=%s&password=%s", channel, sitearg, password);
- }else{
- sprintf(url, "http://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);}
- if(strncmp(result, "OK", 2) && strncmp(result, "RES", 3)){printf("Result not OK\n"); exit(-1);}
- // Find and extract the server responsible for this channel
- char* rtmp=strstr(response, "rtmp='rtmp://");
- if(!rtmp){printf("No rtmp found.\n"); exit(-1);}
- rtmp+=strlen("rtmp='rtmp://");
- 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]);
- printf("Authenticated to broadcast\n");
- }
- free(response);
- return host;
-}
-
-char* getkey(int id, const char* channel)
-{
- char url[snprintf(0,0, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=%s%%5E%s", id, sitearg, channel)+1];
- sprintf(url, "http://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\":\"");
-
- if(!key){return 0;}
- key+=7;
- char* keyend=strchr(key, '"');
- if(!keyend){return 0;}
- char* backslash;
- while((backslash=strchr(key, '\\')))
- {
- memmove(backslash, &backslash[1], strlen(backslash));
- --keyend;
- }
- key=strndup(key, keyend-key);
- free(response);
- return key;
-}
-
-char* getcookie(const char* channel)
-{
- unsigned long long now=time(0);
- char url[strlen("http://tinychat.com/cauth?t=&room=0")+snprintf(0,0, "%llu", now)+strlen(channel)];
- sprintf(url, "http://tinychat.com/cauth?t=%llu&room=%s", now, channel);
- char* response=http_get(url, 0);
- if(strstr(response, "\"lurker\":1"))
- {
- printf("Captcha not completed, you will not be able to send any messages or broadcast\n");
- }
- char* cookie=strstr(response, "\"cookie\":\"");
-
- if(!cookie){return 0;}
- cookie+=10;
- char* end=strchr(cookie, '"');
- if(!end){return 0;}
- cookie=strndup(cookie, end-cookie);
- free(response);
- return cookie;
-}
-
-char* getbroadcastkey(const char* channel, const char* nick, const char* bpassword)
-{
- unsigned int id=idlist_get(nick);
- char url[snprintf(0,0, "http://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://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){free(response); return 0;}
- key+=8;
- char* keyend=strchr(key, '\'');
- if(!keyend){free(response); return 0;}
- key=strndup(key, keyend-key);
- free(response);
- return key;
-}
-
-char* getmodkey(const char* user, const char* pass, const char* channel, char* loggedin)
-{
- // TODO: if possible, do this in a neater way than digging the key out from an HTML page.
- if(!user||!pass){return 0;}
- char post[strlen("form_sent=1&username=&password=&next=http://tinychat.com/0")+strlen(user)+strlen(pass)+strlen(channel)];
- sprintf(post, "form_sent=1&username=%s&password=%s&next=http://tinychat.com/%s", user, pass, channel);
- char* response=http_get("http://tinychat.com/login", post);
- char* key=strstr(response, "autoop: \"");
- if(key)
- {
- key=&key[9];
- char* end=strchr(key, '"');
- if(end)
- {
- end[0]=0;
- key=strdup(key);
- }else{key=0;}
- }
- if(strstr(response, "<div class='name'")){*loggedin=1;}
- free(response);
- return key;
-}
-
-void getcaptcha(void)
-{
- char* url="http://tinychat.com/cauth/captcha";
- char* page=http_get(url, 0);
- char* token=strstr(page, "\"token\":\"");
- if(token)
- {
- token=&token[9];
- char* end=strchr(token, '"');
- if(end)
- {
- end[0]=0;
- printf("Captcha: http://tinychat.com/cauth/recaptcha?token=%s\n", token);
- fflush(stdout);
- fgetc(stdin);
- }
- }
- free(page);
-}
-
char timestampbuf[8];
char* timestamp()
{
@@ -253,47 +99,46 @@ char* timestamp()
return timestampbuf;
}
-char checknick(const char* nick) // Returns zero if the nick is valid, otherwise returning the character that failed the check
+int connectto(const char* host, const char* port)
{
- unsigned int i;
- for(i=0; nick[i]; ++i)
+ struct addrinfo* res;
+ getaddrinfo(host, port, 0, &res);
+ int sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if(connect(sock, res->ai_addr, res->ai_addrlen))
{
- if(!strchr("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-^[]{}`\\|", nick[i])){return nick[i];}
+ perror("Failed to connect");
+ freeaddrinfo(res);
+ return 1;
}
- return 0;
+ freeaddrinfo(res);
+ int i=1;
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
+ return sock;
}
-char* getprivfield(char* nick)
+void registercmd(const char* command, void(*callback)(struct amf*,int), unsigned int minargs)
{
- unsigned int id;
- unsigned int privlen;
- for(privlen=0; nick[privlen]&&nick[privlen]!=' '; ++privlen);
- id=idlist_get(nick);
- if(id<0)
- {
- nick[privlen]=0;
- printf("No such nick: %s\n", nick);
- fflush(stdout);
- return 0;
- }
- char* priv=malloc(snprintf(0, 0, "n%i-", id)+privlen+1);
- sprintf(priv, "n%i-", id); // 'n' for not-broadcasting
- strncat(priv, nick, privlen);
- return priv;
+ ++commandcount;
+ commands=realloc(commands, sizeof(struct command)*commandcount);
+ commands[commandcount-1].command=strdup(command);
+ commands[commandcount-1].callback=callback;
+ commands[commandcount-1].minargs=minargs;
}
-void usage(const char* me)
+static void usage(const char* me)
{
printf("Usage: %s [options] <channelname> <nickname> [channelpassword]\n"
"Options include:\n"
- "-h, --help Show this help text and exit\n"
+ "-h, --help Show this help text and exit.\n"
"-v, --version Show the program version 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"
+ "-s, --site <site> Site to connect to.\n"
" --hexcolors Print hex colors instead of ANSI color codes.\n"
" --cookies <file> File to store cookies in (not having to solve\n"
" the captchas every time)\n"
+ " --greenroom Join a channel's greenroom.\n"
#ifdef RTMP_DEBUG
" --rtmplog <file> Write RTMP input to file.\n"
#endif
@@ -303,13 +148,15 @@ void usage(const char* me)
extern int rtmplog;
#endif
+extern int init_tinychat(const char* chanpass, const char* username, const char* userpass, struct site* site);
+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)
{
- char* channel=0;
- char* nickname=0;
char* password=0;
char* account_user=0;
char* account_pass=0;
+ const char* sitestr=0;
int i;
for(i=1; i<argc; ++i)
{
@@ -334,6 +181,11 @@ int main(int argc, char** argv)
++i;
currentcolor=atoi(argv[i]);
}
+ else if(!strcmp(argv[i], "-s")||!strcmp(argv[i], "--site"))
+ {
+ ++i;
+ sitestr=argv[i];
+ }
else if(!strcmp(argv[i], "--cookies"))
{
++i;
@@ -343,17 +195,22 @@ 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(!strcmp(argv[i], "--greenroom")){greenroom=1;}
else if(!channel){channel=argv[i];}
else if(!nickname){nickname=strdup(argv[i]);}
else if(!password){password=argv[i];}
}
// Check for required arguments
if(!channel||!nickname){usage(argv[0]); return 1;}
- char badchar;
- if((badchar=checknick(nickname)))
+ if(sitestr &&
+ strcmp(sitestr, "tinychat") &&
+ strcmp(sitestr, "kageshi") &&
+ strcmp(sitestr, "kageshicam"))
{
- printf("'%c' is not allowed in nicknames.\n", badchar);
+ printf("Unknown site '%s'. Currently supported sites:\n"
+ "tinychat\n"
+ "kageshi\n"
+ "kageshicam (server/camname/numkey as channel)\n", sitestr);
return 1;
}
setlocale(LC_ALL, "");
@@ -376,120 +233,45 @@ 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
- char* port=strchr(server, ':');
- if(!port){return 3;}
- port[0]=0;
- ++port;
- // Connect
- getaddrinfo(server, port, 0, &res);
- int sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(connect(sock, res->ai_addr, res->ai_addrlen))
+
+ // Accept URL as "channel" and pick site accordingly (in addition to --site)
+ char* domain;
+ if((domain=strstr(channel, "://")))
{
- perror("Failed to connect");
- freeaddrinfo(res);
- return 1;
+ domain=&domain[3];
+ if(!strncmp(domain, "www.", 4)){domain=&domain[4];}
+ if(!strncmp(domain, "tinychat.com/", 13))
+ {
+ sitestr="tinychat";
+ channel=&domain[13];
+ }
+ else if(!strncmp(domain, "kageshi.com/rooms/", 18))
+ {
+ sitestr="kageshi";
+ channel=&domain[18];
+ }
}
- freeaddrinfo(res);
- i=1;
- setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
+ struct site site;
+ int sock;
+ if(!sitestr || !strcmp(sitestr, "tinychat"))
+ {
+ sock=init_tinychat(password, account_user, account_pass, &site);
+ }
+ else if(!strcmp(sitestr, "kageshi"))
+ {
+ sock=init_kageshi(password, account_user, account_pass, &site);
+ }
+ else if(!strcmp(sitestr, "kageshicam"))
+ {
+ sock=init_kageshicam(&site);
+ }
+ free(account_pass);
int random=open("/dev/urandom", O_RDONLY);
- // RTMP handshake
- unsigned char handshake[1536];
- read(random, handshake, 1536);
if(currentcolor>=COLORCOUNT)
{
read(random, ¤tcolor, sizeof(currentcolor));
}
close(random);
- write(sock, "\x03", 1); // Send 0x03 and 1536 bytes of random junk
- write(sock, handshake, 1536);
- read(sock, handshake, 1); // Receive server's 0x03+junk
- b_read(sock, handshake, 1536);
- write(sock, handshake, 1536); // Send server's junk back
- 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};
- if(!loggedin){free(account_pass); account_user=0; account_pass=0;}
- getcaptcha();
- char* cookie=getcookie(channel);
- // Send connect request
- struct rtmp amf;
- amfinit(&amf, 3);
- amfstring(&amf, "connect");
- amfnum(&amf, 0);
- amfobjstart(&amf);
- amfobjitem(&amf, "app");
- amfstring(&amf, "tinyconf");
-
- amfobjitem(&amf, "flashVer");
- amfstring(&amf, "no flash");
-
- amfobjitem(&amf, "swfUrl");
- amfstring(&amf, "no flash");
-
- amfobjitem(&amf, "tcUrl");
- char str[strlen("rtmp://:/tinyconf0")+strlen(server)+strlen(port)];
- sprintf(str, "rtmp://%s:%s/tinyconf", server, port);
- amfstring(&amf, str);
-
- amfobjitem(&amf, "fpad");
- amfbool(&amf, 0);
-
- amfobjitem(&amf, "capabilities");
- amfnum(&amf, 0);
-
- amfobjitem(&amf, "audioCodecs");
- amfnum(&amf, 0);
-
- amfobjitem(&amf, "videoCodecs");
- amfnum(&amf, 0);
-
- amfobjitem(&amf, "videoFunction");
- amfnum(&amf, 0);
-
- amfobjitem(&amf, "pageUrl");
- char pageurl[strlen("http://tinychat.com/0") + strlen(channel)];
- sprintf(pageurl, "http://tinychat.com/%s", channel);
- amfstring(&amf, pageurl);
-
- amfobjitem(&amf, "objectEncoding");
- amfnum(&amf, 0);
- amfobjend(&amf);
- amfobjstart(&amf);
- amfobjitem(&amf, "version");
- amfstring(&amf, "Desktop");
-
- amfobjitem(&amf, "type");
- amfstring(&amf, "default"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to, but "default" seems to work fine too.
-
- amfobjitem(&amf, "account");
- amfstring(&amf, account_user?account_user:"");
-
- amfobjitem(&amf, "prefix");
- amfstring(&amf, sitearg);
-
- amfobjitem(&amf, "room");
- amfstring(&amf, channel);
-
- if(modkey)
- {
- amfobjitem(&amf, "autoop");
- amfstring(&amf, modkey);
- }
- amfobjitem(&amf, "cookie");
- amfstring(&amf, cookie);
- amfobjend(&amf);
- amfsend(&amf, sock);
- free(modkey);
- free(cookie);
-
- char* unban=0;
struct pollfd pfd[2];
pfd[0].fd=0;
pfd[0].events=POLLIN;
@@ -497,6 +279,7 @@ int main(int argc, char** argv)
pfd[1].fd=sock;
pfd[1].events=POLLIN;
pfd[1].revents=0;
+ struct rtmp rtmp={0,0,0,0,0};
while(1)
{
// Poll for input, very crude chat UI
@@ -517,7 +300,6 @@ int main(int argc, char** argv)
while(len>0 && (buf[len-1]=='\n'||buf[len-1]=='\r')){--len;}
if(!len){continue;} // Don't send empty lines
buf[len]=0;
- char* privfield=0;
if(buf[0]=='/') // Got a command
{
if(!strcmp(buf, "/help"))
@@ -577,93 +359,47 @@ int main(int argc, char** argv)
}
else if(!strncmp(buf, "/nick ", 6))
{
- if((badchar=checknick(&buf[6])))
- {
- printf("'%c' is not allowed in nicknames.\n", badchar);
- continue;
- }
- amfinit(&amf, 3);
- amfstring(&amf, "nick");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, &buf[6]);
- amfsend(&amf, sock);
+ site.nick(sock, &buf[6]);
continue;
}
else if(!strncmp(buf, "/msg ", 5))
{
- privfield=getprivfield(&buf[5]);
- if(!privfield){continue;}
- }
- else if(!strncmp(buf, "/priv ", 6))
- {
- char* end=strchr(&buf[6], ' ');
- if(!end){continue;}
- privfield=getprivfield(&buf[6]);
- if(!privfield){continue;}
- len=strlen(&end[1]);
- memmove(buf, &end[1], len+1);
+ char* msg=strchr(&buf[5], ' ');
+ if(msg)
+ {
+ msg[0]=0;
+ site.sendpm(sock, &msg[1], &buf[5]);
+ continue;
+ }
}
else if(!strncmp(buf, "/opencam ", 9))
{
- stream_start(&buf[9], sock);
+ site.opencam(sock, &buf[9]);
continue;
}
else if(!strncmp(buf, "/closecam ", 10))
{
- stream_stopvideo(sock, idlist_get(&buf[10]));
+ site.closecam(sock, &buf[10]);
continue;
}
else if(!strncmp(buf, "/close ", 7)) // Stop someone's cam/mic broadcast
{
- char nick[strlen(&buf[7])+1];
- strcpy(nick, &buf[7]);
- amfinit(&amf, 2);
- amfstring(&amf, "owner_run");
- amfnum(&amf, 0);
- amfnull(&amf);
- sprintf(buf, "_close%s", nick);
- amfstring(&amf, buf);
- amfsend(&amf, sock);
- len=sprintf(buf, "closed: %s", nick);
+ site.mod_close(sock, &buf[7]);
+ continue;
}
else if(!strncmp(buf, "/ban ", 5)) // Ban someone
{
- char nick[strlen(&buf[5])+1];
- strcpy(nick, &buf[5]);
- amfinit(&amf, 3);
- amfstring(&amf, "owner_run");
- amfnum(&amf, 0);
- amfnull(&amf);
- sprintf(buf, "notice%s%%20was%%20banned%%20by%%20%s%%20(%s)", nick, nickname, account_user);
- amfstring(&amf, buf);
- amfsend(&amf, sock);
- printf("%s %s was banned by %s (%s)\n", timestamp(), nick, nickname, account_user);
- // kick (this does the actual banning)
- amfinit(&amf, 3);
- amfstring(&amf, "kick");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, nick);
- sprintf(buf, "%i", idlist_get(nick));
- amfstring(&amf, buf);
- amfsend(&amf, sock);
+ site.mod_ban(sock, &buf[5]);
continue;
}
- else if(!strcmp(buf, "/banlist") || !strncmp(buf, "/forgive ", 9))
+ else if(!strcmp(buf, "/banlist"))
{
- free(unban);
- if(buf[1]=='f') // forgive
- {
- unban=strdup(&buf[9]);
- }else{
- unban=0;
- }
- amfinit(&amf, 3);
- amfstring(&amf, "banlist");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfsend(&amf, sock);
+ site.mod_banlist(sock);
+ continue;
+ }
+ else if(!strncmp(buf, "/forgive ", 9))
+ {
+ site.mod_unban(sock, &buf[9]);
continue;
}
else if(!strcmp(buf, "/names"))
@@ -679,42 +415,22 @@ int main(int argc, char** argv)
}
else if(!strcmp(buf, "/mute"))
{
- amfinit(&amf, 3);
- amfstring(&amf, "owner_run");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, "mute");
- amfsend(&amf, sock);
+ site.mod_mute(sock);
continue;
}
else if(!strcmp(buf, "/push2talk"))
{
- amfinit(&amf, 3);
- amfstring(&amf, "owner_run");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, "push2talk");
- amfsend(&amf, sock);
+ site.mod_push2talk(sock);
continue;
}
else if(!strcmp(buf, "/camup"))
{
- // Retrieve and send the key for broadcasting access
- char* key=getbroadcastkey(channel, nickname, bpassword);
- amfinit(&amf, 3);
- amfstring(&amf, "bauth");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, key);
- amfsend(&amf, sock);
- free(key);
- // Initiate stream
- streamout_start(idlist_get(nickname), sock);
+ site.camup(sock);
continue;
}
else if(!strcmp(buf, "/camdown"))
{
- stream_stopvideo(sock, idlist_get(nickname));
+ site.camdown(sock);
continue;
}
else if(!strncmp(buf, "/video ", 7)) // Send video data
@@ -735,13 +451,7 @@ int main(int argc, char** argv)
}
else if(!strncmp(buf, "/topic ", 7))
{
- amfinit(&amf, 3);
- amfstring(&amf, "topic");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, &buf[7]);
- amfstring(&amf, "");
- amfsend(&amf, sock);
+ site.mod_topic(sock, &buf[7]);
continue;
}
else if(!strncmp(buf, "/whois ", 7)) // Get account username
@@ -763,10 +473,8 @@ int main(int argc, char** argv)
}
else if(!strncmp(buf, "/allow ", 7))
{
- if(!bpassword){continue;}
- privfield=getprivfield(&buf[7]);
- if(!privfield){continue;}
- len=sprintf(buf, "/allowbroadcast %s", bpassword);
+ site.mod_allowbroadcast(sock, &buf[7]);
+ continue;
}
else if(!strncmp(buf, "/getnick ", 9))
{
@@ -776,32 +484,7 @@ int main(int argc, char** argv)
continue;
}
}
- char* msg=tonumlist(buf, len);
- amfinit(&amf, 3);
- amfstring(&amf, "privmsg");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, msg);
- 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)
- {
- amfstring(&amf, privfield);
- // And one in case they're broadcasting
- privfield[0]='b'; // 'b' for broadcasting
- struct rtmp bamf;
- amfinit(&bamf, 3);
- amfstring(&bamf, "privmsg");
- amfnum(&bamf, 0);
- amfnull(&bamf);
- amfstring(&bamf, msg);
- amfstring(&bamf, colors[currentcolor%COLORCOUNT]);
- amfstring(&bamf, privfield);
- amfsend(&bamf, sock);
- free(privfield);
- }
- amfsend(&amf, sock);
- free(msg);
+ site.sendmessage(sock, buf);
continue;
}
// Got data from server
@@ -813,328 +496,16 @@ int main(int argc, char** argv)
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);
- if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING)
- {
-// printf("Got command: '%s'\n", amfin->items[0].string.string);
- if(!strcmp(amfin->items[0].string.string, "_error"))
- printamf(amfin);
- }
- // Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
- 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[4].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
- {
- size_t len;
- char* msg=fromnumlist(amfin->items[3].string.string, &len);
- const char* color=color_start(amfin->items[4].string.string);
- char* line=msg;
- while(line)
- {
- // Handle multi-line messages
- char* nextline=0;
- unsigned int linelen;
- for(linelen=0; &line[linelen]<&msg[len]; ++linelen)
- {
- if(line[linelen]=='\r' || line[linelen]=='\n'){nextline=&line[linelen+1]; break;}
- }
- printf("%s %s%s: ", timestamp(), color, amfin->items[5].string.string);
- fwrite(line, linelen, 1, stdout);
- printf("%s\n", color_end());
- line=nextline;
- }
- char* response=0;
- if(!strncmp(msg, "/allowbroadcast ", 16))
- {
- if(getbroadcastkey(channel, nickname, &msg[16])) // Validate password
- {
- free((void*)bpassword);
- bpassword=strdup(&msg[16]);
- printf("Authenticated to broadcast\n");
- }
- }
- else if(!strcmp(msg, "/version"))
- {
- response=tonumlist("/version tc_client-" VERSION, strlen(VERSION)+19);
- }
- if(response)
- {
- // Send our command reponse with a privacy field
- amfinit(&amf, 3);
- amfstring(&amf, "privmsg");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, response);
- amfstring(&amf, "#0,en");
- int id=idlist_get(amfin->items[5].string.string);
- char priv[snprintf(0, 0, "n%i-%s", id, amfin->items[5].string.string)+1];
- sprintf(priv, "n%i-%s", id, amfin->items[5].string.string);
- amfstring(&amf, priv);
- amfsend(&amf, sock);
- // And again in case they're broadcasting
- amfinit(&amf, 3);
- amfstring(&amf, "privmsg");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, response);
- amfstring(&amf, "#0,en");
- priv[0]='b';
- amfstring(&amf, priv);
- amfsend(&amf, sock);
- free(response);
- }
- free(msg);
- fflush(stdout);
- }
- // users on channel entry. there's also a "joinsdone" command for some reason...
- else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && (amf_comparestrings_c(&amfin->items[0].string, "joins") || amf_comparestrings_c(&amfin->items[0].string, "join") || amf_comparestrings_c(&amfin->items[0].string, "registered")))
- {
- if(amf_comparestrings_c(&amfin->items[0].string, "joins"))
- {
- printf("Currently online: ");
- }else{
- printf("%s ", timestamp());
- }
- int i;
- for(i = 2; i < amfin->itemcount; ++i)
- {
- if(amfin->items[i].type==AMF_OBJECT)
- {
- struct amfitem* item=amf_getobjmember(&amfin->items[i].object, "id");
- if(item->type!=AMF_NUMBER){continue;}
- int id=item->number;
- item=amf_getobjmember(&amfin->items[i].object, "nick");
- if(item->type!=AMF_STRING){continue;}
- const char* nick=item->string.string;
- item=amf_getobjmember(&amfin->items[i].object, "account");
- if(item->type!=AMF_STRING){continue;}
- const char* account=(item->string.string[0]?item->string.string:0);
- item=amf_getobjmember(&amfin->items[i].object, "mod");
- if(item->type!=AMF_BOOL){continue;}
- char mod=item->boolean;
- idlist_add(id, nick, account, mod);
- printf("%s%s", (i==2?"":", "), nick);
- if(amf_comparestrings_c(&amfin->items[0].string, "registered"))
- {
- // Tell the server how often to let us know it got our packets
- struct rtmp setbw={
- .type=RTMP_SERVER_BW,
- .chunkid=2,
- .length=4,
- .msgid=0,
- .buf="\x00\x00\x40\x00" // Every 0x4000 bytes
- };
- rtmp_send(sock, &setbw);
- char* key=getkey(id, channel);
- curl_easy_cleanup(curl); // At this point we should be done with HTTP requests
- curl=0;
- if(!key){printf("Failed to get channel key\n"); return 1;}
- amfinit(&amf, 3);
- amfstring(&amf, "cauth");
- amfnum(&amf, 0);
- amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
- amfstring(&amf, key);
- amfsend(&amf, sock);
- free(key);
- // Set the given nickname
- amfinit(&amf, 3);
- amfstring(&amf, "nick");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, nickname);
- amfsend(&amf, sock);
- // Keep what the server gave us, just in case
- free(nickname);
- nickname=strdup(nick);
- }
- }
- }
- if(amf_comparestrings_c(&amfin->items[0].string, "joins"))
- {
- printf("\n");
- for(i=0; i<idlistlen; ++i)
- {
- if(idlist[i].op){printf("%s is a moderator.\n", idlist[i].name);}
- }
- printf("Currently on cam: ");
- char first=1;
- for(i = 2; i < amfin->itemcount; ++i)
- {
- if(amfin->items[i].type==AMF_OBJECT)
- {
- struct amfitem* item=amf_getobjmember(&amfin->items[i].object, "bf");
- if(item->type!=AMF_BOOL || !item->boolean){continue;}
- item=amf_getobjmember(&amfin->items[i].object, "nick");
- if(item->type!=AMF_STRING){continue;}
- printf("%s%s", (first?"":", "), item->string.string);
- first=0;
- }
- }
- printf("\n");
- }else{
- printf(" entered the channel\n");
- if(idlist[idlistlen-1].op){printf("%s is a moderator.\n", idlist[idlistlen-1].name);}
- if(amf_comparestrings_c(&amfin->items[0].string, "registered"))
- {
- printf("Connection ID: %i\n", idlist[0].id);
- }
- }
- fflush(stdout);
- }
- // quit/part
- else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "quit") && amfin->items[2].type==AMF_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)
+ for(i=0; i<commandcount; ++i)
{
- if(!strcmp(amfin->items[2].string.string, nickname)) // Successfully changed our own nickname
+ if(amfin->itemcount>=commands[i].minargs && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, commands[i].command))
{
- free(nickname);
- nickname=strdup(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, mute, push2talk, closing cams
- 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);
+ commands[i].callback(amfin, sock);
fflush(stdout);
- }
- else if(!strcmp("mute", amfin->items[2].string.string))
- {
- printf("%s Non-moderators have been temporarily muted.\n", timestamp());
- fflush(stdout);
- }
- else if(!strcmp("push2talk", amfin->items[2].string.string))
- {
- printf("%s Push to talk request has been sent to non-moderators.\n", timestamp());
- fflush(stdout);
- }
- 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");
- fflush(stdout);
- stream_stopvideo(sock, idlist_get(nickname));
- }
- }
- // nickinuse, the nick we wanted to change to is already taken
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nickinuse"))
- {
- printf("Nick is already in use.\n");
- fflush(stdout);
- }
- // Room topic
- else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "topic") && amfin->items[2].type==AMF_STRING && strlen(amfin->items[2].string.string) > 0)
- {
- printf("Room topic: %s\n", amfin->items[2].string.string);
- fflush(stdout);
- }
- // Get list of banned users
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "banlist"))
- {
- unsigned int i;
- if(unban) // This is not a response to /banlist but to /forgive
- {
- for(i=2; i+1<amfin->itemcount; i+=2)
- {
- if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
- if(!strcmp(amfin->items[i+1].string.string, unban) || !strcmp(amfin->items[i].string.string, unban))
- {
- amfinit(&amf, 3);
- amfstring(&amf, "forgive");
- amfnum(&amf, 0);
- amfnull(&amf);
- amfstring(&amf, amfin->items[i].string.string);
- amfsend(&amf, sock);
- }
- // If the nickname isn't found in the banlist we assume it's an ID
- }
- continue;
- }
- printf("Banned users:\n");
- printf("ID Nickname\n");
- for(i=2; i+1<amfin->itemcount; i+=2)
- {
- if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
- unsigned int len=printf("%s", amfin->items[i].string.string);
- for(;len<10; ++len){printf(" ");}
- printf(" %s\n", amfin->items[i+1].string.string);
- }
- printf("Use /forgive <ID/nick> to unban someone\n");
- fflush(stdout);
- }
- else if(amfin->itemcount>4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "notice") && amfin->items[2].type==AMF_STRING && amf_comparestrings_c(&amfin->items[2].string, "avon"))
- {
- if(amfin->items[4].type==AMF_STRING)
- {
- printf("%s cammed up\n", amfin->items[4].string.string);
- fflush(stdout);
- }
- }
- // Handle results for various requests, haven't seen much of a pattern to it, always successful?
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "_result"))
- {
- // Creating a new stream worked, now play media (cam/mic) on it (if that's what the result was for)
- stream_play(amfin, sock);
- }
- else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "onStatus"))
- {
- stream_handlestatus(amfin, sock);
- }
- else if(amfin->itemcount>2 && amfin->items[0].type==AMF_STRING && amfin->items[2].type==AMF_OBJECT && amf_comparestrings_c(&amfin->items[0].string, "account"))
- {
- struct amfitem* id=amf_getobjmember(&amfin->items[2].object, "id");
- struct amfitem* account=amf_getobjmember(&amfin->items[2].object, "account");
- if(id && account && id->type==AMF_NUMBER && account->type==AMF_STRING)
- {
- for(i=0; i<idlistlen; ++i)
- {
- if(id->number==idlist[i].id)
- {
- if(strcmp(account->string.string, "$noinfo"))
- {
- printf("%s is logged in as %s\n", idlist[i].name, account->string.string);
- }else{
- printf("%s is not logged in\n", idlist[i].name);
- }
- fflush(stdout);
- break;
- }
- }
+ break;
}
}
- // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
+ // if(i==commandcount){printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
free(rtmp.buf);
diff --git a/client.h b/client.h
new file mode 100644
index 0000000..1c3f312
--- /dev/null
+++ b/client.h
@@ -0,0 +1,27 @@
+#include "amfparser.h"
+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);
+};
+
+extern char greenroom;
+extern char* channel;
+extern char* nickname;
+extern char* http_get(const char* url, const char* post);
+extern char* timestamp();
+extern int connectto(const char* host, const char* port);
+extern void registercmd(const char* command, void(*callback)(struct amf*,int), unsigned int minargs);
diff --git a/colors.c b/colors.c
index 8b8a489..d29d3d4 100644
--- a/colors.c
+++ b/colors.c
@@ -65,7 +65,7 @@ const char* resolvecolor(const char* tc_color)
int i;
for(i=0; i<COLORCOUNT; ++i)
{
- if(!strcmp(colors[i], tc_color)){return termcolors[i];}
+ if(!strncmp(colors[i], tc_color, 7)){return termcolors[i];}
}
return "0";
}
diff --git a/configure b/configure
index 2e5b8d3..e47dc2d 100755
--- a/configure
+++ b/configure
@@ -244,6 +244,11 @@ if ! testbuild 'prctl' 'prctl(PR_SET_PDEATHSIG, SIGHUP);return 0;' 'sys/prctl.h
echo '#define NO_PRCTL 1' >> config.h
fi
+if testpkgconfig glib-2.0 GLIB; then
+ echo 'LIBS+=$(GLIB_LIBS)' >> config.mk
+ echo 'CFLAGS+=$(GLIB_CFLAGS) -DHAVE_GLIB=1' >> config.mk
+fi
+
printf 'Checking if we have a working poll()... '
echo '#include <poll.h>' > polltest.c
echo '#include <sys/socket.h>' >> polltest.c
@@ -259,11 +264,7 @@ if ./polltest 2> /dev/null; then
echo yes
else
echo no
- if testpkgconfig glib-2.0 GLIB; then
- echo '#define POLL_BROKEN_OR_MISSING 1' >> config.h
- echo 'LIBS+=$(GLIB_LIBS)' >> config.mk
- echo 'CFLAGS+=$(GLIB_CFLAGS)' >> config.mk
- fi
+ echo '#define POLL_BROKEN_OR_MISSING 1' >> config.h
fi
rm -f polltest.c polltest
diff --git a/kageshi.c b/kageshi.c
new file mode 100644
index 0000000..5e4b307
--- /dev/null
+++ b/kageshi.c
@@ -0,0 +1,469 @@
+/*
+ tc_client, a simple non-flash client for tinychat(.com)
+ Copyright (C) 2016 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 <stdio.h>
+#include <string.h>
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+#include "media.h"
+#include "amfwriter.h"
+#include "rtmp.h"
+#include "client.h"
+#include "idlist.h"
+#include "colors.h"
+
+static char* joinkey=0;
+static unsigned int joinkeynum;
+
+static void joindata(struct amf* amfin, int sock)
+{
+ if(amfin->items[6].type!=AMF_STRING || amfin->items[12].type!=AMF_NUMBER){return;}
+ joinkey=strdup(amfin->items[6].string.string);
+ joinkeynum=amfin->items[12].number;
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "connectionOK");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, joinkeynum);
+ amfstring(&amf, joinkey); // Channel ID or something?
+ amfstring(&amf, nickname);
+ amfstring(&amf, ""); // Unidentified number string, doesn't seem to matter at all
+ amfsend(&amf, sock);
+}
+
+static void joinuser(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type==AMF_STRING)
+ {
+ printf("%s %s entered the channel\n", timestamp(), amfin->items[2].string.string);
+ }
+}
+
+static void receivepublicmsg(struct amf* amfin, int sock)
+{
+ if(amfin->items[3].type!=AMF_STRING || amfin->items[4].type!=AMF_STRING || amfin->items[5].type!=AMF_STRING){return;}
+ if(!strcmp(amfin->items[3].string.string, nickname)){return;} // It's the message we just sent
+ const char* color=color_start(amfin->items[5].string.string);
+ printf("%s %s%s: %s%s\n", timestamp(), color, amfin->items[3].string.string, amfin->items[4].string.string, color_end());
+}
+
+static void removeuser(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING){return;}
+ idlist_remove(amfin->items[2].string.string);
+ printf("%s %s left the channel\n", timestamp(), amfin->items[2].string.string);
+}
+
+static void senduserlist(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING || amfin->items[2].string.string[1]!='"'){return;}
+ printf("Currently online: ");
+// "u" seems to be timestamp of status update
+// "l" seems to be timestamp of connection
+// "m" seems to be a status message (showed up as "BRB" once)
+ char* nick=&amfin->items[2].string.string[2];
+ char first=1;
+ while(nick)
+ {
+ if(first){first=0;}else{printf(", "); nick=&nick[3];}
+ char* end=strchr(nick, '"');
+ if(!end){break;}
+ end[0]=0;
+ printf("%s", nick);
+ idlist_add(0, nick, 0, 0);
+ nick=strstr(&end[1], "\",\"");
+ }
+ printf("\n");
+}
+
+/*
+Unknown command...
+amf: 0x1eca070
+amf->itemcount: 3
+String: 'camList'
+Number: 5.000000
+String: '{"1":"{\"camno\":1,\"server\":\"<IP>\",\"server_name\":\"<name>\",\"server_location\":\"<location>\",\"camname\":\"<camid>\",\"username\":\"<nickname/username>\"}","2":"{\"camno\":2,\"server\":\"<IP>\",\"server_name\":\"<name>\",\"server_location\":\"<location>\",\"camname\":\"<camid>\",\"username\":\"<nickname/username>\"}","3":etc...}'
+*/
+
+static void startcam(struct amf* amfin, int sock)
+{
+ if(amfin->items[3].type!=AMF_STRING || amfin->items[4].type!=AMF_STRING || amfin->items[5].type!=AMF_STRING){return;}
+ printf("%s cammed up on %s/%s/%u as %s\n", amfin->items[4].string.string, amfin->items[5].string.string, channel, joinkeynum, amfin->items[3].string.string);
+}
+
+static void stopcam(struct amf* amfin, int sock)
+{
+ if(amfin->items[3].type!=AMF_STRING || amfin->items[4].type!=AMF_STRING){return;}
+ printf("%s cammed down\n", amfin->items[4].string.string);
+// TODO: Send a VideoEnd notice?
+}
+
+static void pmreceive(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING || amfin->items[3].type!=AMF_STRING || amfin->items[4].type!=AMF_STRING || amfin->items[5].type!=AMF_STRING){return;}
+ if(!strcmp(amfin->items[2].string.string, nickname)){return;} // It's the message we just sent
+ const char* color=color_start(amfin->items[5].string.string);
+ printf("%s %s%s: /msg %s %s%s\n", timestamp(), color, amfin->items[2].string.string, amfin->items[3].string.string, amfin->items[4].string.string, color_end());
+}
+
+static void typingpm(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING){return;}
+ printf("%s is typing a PM\n", amfin->items[2].string.string);
+}
+
+static void kicked(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING || amfin->items[3].type!=AMF_STRING || amfin->items[7].type!=AMF_STRING){return;}
+ printf("%s %s was kicked by %s (%s)\n", timestamp(), amfin->items[3].string.string, amfin->items[2].string.string, amfin->items[7].string.string);
+ idlist_remove(amfin->items[3].string.string);
+ printf("%s %s left the channel\n", timestamp(), amfin->items[3].string.string);
+}
+
+static void result2(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_OBJECT){return;}
+ struct amfitem* desc=amf_getobjmember(&amfin->items[2].object, "description");
+ if(desc->type!=AMF_STRING){return;}
+ if(!strcmp(desc->string.string, "{\"param\":\"\",\"reject\":\"0006\"}"))
+ {
+ printf("Wrong username/password\n");
+ }
+ else if(!strcmp(desc->string.string, "{\"param\":\"\",\"reject\":\"0008\"}"))
+ {
+ printf("Password required\n");
+ }
+ else if(!strcmp(desc->string.string, "{\"param\":\"\",\"reject\":\"0012\"}"))
+ {
+ printf("Account required\n");
+ }
+ else if(!strcmp(desc->string.string, "{\"param\":\"\",\"reject\":\"0003\"}"))
+ {
+ printf("Nickname unavailable\n");
+ }
+ else if(!strcmp(desc->string.string, "{\"param\":\"\",\"reject\":\"0005\"}"))
+ {
+ printf("Nickname too short\n");
+ }
+}
+
+static void sendmessage(int sock, const char* buf)
+{
+ char color[8];
+ memcpy(color, colors[currentcolor%COLORCOUNT], 7);
+ color[7]=0;
+ if(color[5]==',') // That one stupid tinychat color
+ {
+ strcpy(color, "#00a990");
+ }
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "send_public");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, joinkeynum);
+ amfstring(&amf, joinkey);
+ amfstring(&amf, nickname); // Spoofable?
+ amfstring(&amf, buf);
+ amfstring(&amf, "0");
+ amfstring(&amf, color);
+ amfstring(&amf, "1");
+ amfnum(&amf, 1);
+ amfsend(&amf, sock);
+}
+
+/* Not used yet
+static void sendpmtyping(int sock, const char* nick)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "typing_pm");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, joinkeynum);
+ amfstring(&amf, joinkey);
+ amfstring(&amf, nickname); // Spoofable?
+ amfstring(&amf, nick);
+ amfstring(&amf, "1"); // 0 for not typing anymore? (or 2? from observations)
+ amfsend(&amf, sock);
+}
+*/
+
+static void sendpm(int sock, const char* buf, const char* nick)
+{
+ char color[8];
+ memcpy(color, colors[currentcolor%COLORCOUNT], 7);
+ color[7]=0;
+ if(color[5]==',') // That one stupid tinychat color
+ {
+ strcpy(color, "#00a990");
+ }
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "pm");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, joinkeynum);
+ amfstring(&amf, joinkey);
+ amfstring(&amf, nickname);
+ amfstring(&amf, nick);
+ amfstring(&amf, buf);
+ amfstring(&amf, color);
+ amfstring(&amf, "1");
+ amfnum(&amf, 1);
+ amfsend(&amf, sock);
+}
+
+static void notimplemented()
+{
+ printf("The requested feature is either not supported by kageshi or not yet implemented in tc_client's kageshi support\n");
+}
+
+int init_kageshi(const char* chanpass, const char* username, const char* userpass, struct site* site)
+{
+#ifndef HAVE_GLIB
+ if(username){printf("Account support requires glib (for md5sums)\n");}
+ username=0;
+#endif
+ registercmd("joinData", joindata, 13);
+ registercmd("joinuser", joinuser, 3);
+ registercmd("receivePublicMsg", receivepublicmsg, 6);
+ registercmd("removeuser", removeuser, 3);
+ registercmd("sendUserList", senduserlist, 3);
+ registercmd("startCam", startcam, 6);
+ registercmd("stopCam", stopcam, 5);
+ registercmd("pmReceive", pmreceive, 6);
+ registercmd("typingPM", typingpm, 3);
+ registercmd("kicked", kicked, 8);
+ registercmd("_result", result2, 3);
+ site->sendmessage=sendmessage;
+ site->sendpm=sendpm;
+ site->nick=notimplemented;
+ 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;
+ int sock=connectto("107.191.96.85", "1935");
+ rtmp_handshake(sock);
+ // Send connect request
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "connect");
+ amfnum(&amf, username?1:0);
+ amfobjstart(&amf);
+ amfobjitem(&amf, "app");
+ {char str[strlen("chat/0")+strlen(channel)];
+ sprintf(str, "chat/%s", channel);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "flashVer");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "swfUrl");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "tcUrl");
+ {char str[strlen("rtmp://107.191.96.85:1935/chat/0")+strlen(channel)];
+ sprintf(str, "rtmp://107.191.96.85:1935/chat/%s", channel);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "fpad");
+ amfbool(&amf, 0);
+
+ amfobjitem(&amf, "capabilities");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "audioCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoFunction");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "pageUrl");
+ char pageurl[strlen("https://kageshi.com/rooms/0") + strlen(channel)];
+ sprintf(pageurl, "https://kageshi.com/rooms/%s", channel);
+ amfstring(&amf, pageurl);
+
+ amfobjitem(&amf, "objectEncoding");
+ amfnum(&amf, 0);
+ amfobjend(&amf);
+ amfstring(&amf, "connect");
+ amfstring(&amf, "");
+ amfstring(&amf, username?"":nickname);
+ if(username)
+ {
+ #ifdef HAVE_GLIB
+ GChecksum* md5=g_checksum_new(G_CHECKSUM_MD5);
+ g_checksum_update(md5, (void*)userpass, strlen(userpass));
+ amfstring(&amf, username);
+ amfstring(&amf, g_checksum_get_string(md5));
+ g_checksum_free(md5);
+ #endif
+ }else{
+ amfstring(&amf, "");
+ amfstring(&amf, "");
+ }
+ amfstring(&amf, channel);
+ // Not sure what these are, they don't seem to do anything, but need to be in the right format. kinda looks like HMAC
+ amfstring(&amf, "00000000-0000-0000-0000-000000000000-00000000-0000-0000-0000-000000000000");
+ amfstring(&amf, "00000000-0000-0000-0000-000000000000-00000000-0000-0000-0000-000000000000");
+ amfstring(&amf, "0.31");
+ amfstring(&amf, chanpass?chanpass:"");
+ amfstring(&amf, "");
+ amfbool(&amf, !!chanpass);
+ amfsend(&amf, sock);
+ return sock;
+}
+
+static void result(struct amf* amfin, int sock)
+{
+ // Handle results for various requests, haven't seen much of a pattern to it, always successful?
+ // Creating a new stream worked, now play media (cam/mic) on it (if that's what the result was for)
+ stream_play(amfin, sock);
+ // Tell them to actually send us media
+ unsigned int i;
+ for(i=0; i<streamcount; ++i)
+ {
+ if(streams[i].streamid==amfin->items[2].number)
+ {
+ struct rtmp amf;
+ amfinit(&amf, streams[i].streamid*6+2);
+ amfstring(&amf, "receiveVideo");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfbool(&amf, 1);
+ amfsend(&amf, sock);
+ amfinit(&amf, streams[i].streamid*6+2);
+ amfstring(&amf, "receiveAudio");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfbool(&amf, 1);
+ amfsend(&amf, sock);
+ }
+ }
+}
+
+static void opencam(int sock, const char* camname)
+{
+ stream_start(camname, camname, sock);
+}
+
+static void closecam(int sock, const char* camname)
+{
+ stream_stopvideo(sock, camname);
+}
+
+static void notimplemented2()
+{
+ printf("The requested feature is either not supported by kageshi cam sessions or not yet implemented in tc_client's kageshi cam support\n");
+}
+
+int init_kageshicam(struct site* site)
+{
+ // Separate server from channel name
+ char server[strlen(channel)+1];
+ strcpy(server, channel);
+ char* channelname=strchr(server, '/');
+ if(!channelname){return -1;} // TODO: Useful error
+ channelname[0]=0;
+ channelname=&channelname[1];
+ // Separate the channel name from the numeric key
+ char* key=strchr(channelname, '/');
+ if(!key){return -1;} // TODO: Useful error
+ key[0]=0;
+ joinkeynum=strtoul(&key[1], 0, 0);
+
+ registercmd("_result", result, 3);
+ site->opencam=opencam;
+ site->closecam=closecam;
+ site->sendmessage=notimplemented2;
+ site->sendpm=notimplemented2;
+ site->nick=notimplemented2;
+ site->mod_close=notimplemented2;
+ site->mod_ban=notimplemented2;
+ site->mod_banlist=notimplemented2;
+ site->mod_unban=notimplemented2;
+ site->mod_mute=notimplemented2;
+ site->mod_push2talk=notimplemented2;
+ site->mod_topic=notimplemented2;
+ site->mod_allowbroadcast=notimplemented2;
+ site->camup=notimplemented2;
+ site->camdown=notimplemented2;
+ int sock=connectto(server, "1935");
+ rtmp_handshake(sock);
+ // Send connect request
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "connect");
+ amfnum(&amf, 0);
+ amfobjstart(&amf);
+ amfobjitem(&amf, "app");
+ {char str[snprintf(0,0,"CamServer/%u", joinkeynum)+1];
+ sprintf(str, "CamServer/%u", joinkeynum);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "flashVer");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "swfUrl");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "tcUrl");
+ {char str[snprintf(0,0,"rtmp://%s:1935/CamServer/%u", server, joinkeynum)+1];
+ sprintf(str, "rtmp://%s:1935/CamServer/%u", server, joinkeynum);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "fpad");
+ amfbool(&amf, 0);
+
+ amfobjitem(&amf, "capabilities");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "audioCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoFunction");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "pageUrl");
+ char pageurl[strlen("http://kageshi.com/rooms/0") + strlen(channelname)];
+ sprintf(pageurl, "http://kageshi.com/rooms/%s", channelname);
+ amfstring(&amf, pageurl);
+
+ amfobjitem(&amf, "objectEncoding");
+ amfnum(&amf, 0);
+ amfobjend(&amf);
+ amfstring(&amf, "connect");
+ amfstring(&amf, "0.1");
+ amfsend(&amf, sock);
+ return sock;
+}
diff --git a/media.c b/media.c
index 0776545..255dc2d 100644
--- a/media.c
+++ b/media.c
@@ -36,14 +36,13 @@ char stream_idtaken(unsigned int id)
return 0;
}
-void stream_start(const char* nick, int sock) // called upon privmsg "/opencam ..."
+void stream_start(const char* nick, const char* camid, int sock) // called upon privmsg "/opencam ..."
{
- unsigned int userid=idlist_get(nick);
unsigned int streamid=1;
while(stream_idtaken(streamid)){++streamid;}
++streamcount;
streams=realloc(streams, sizeof(struct stream)*streamcount);
- streams[streamcount-1].userid=userid;
+ streams[streamcount-1].camid=strdup(camid);
streams[streamcount-1].streamid=streamid;
streams[streamcount-1].outgoing=0;
struct rtmp amf;
@@ -52,17 +51,17 @@ void stream_start(const char* nick, int sock) // called upon privmsg "/opencam .
amfnum(&amf, streamid+1);
amfnull(&amf);
amfsend(&amf, sock);
- printf("Starting media stream for %s (%u)\n", nick, userid);
+ printf("Starting media stream for %s (%s)\n", nick, camid);
fflush(stdout);
}
-void streamout_start(unsigned int id, int sock) // called upon privmsg "/camup"
+void streamout_start(const char* id, int sock) // called upon privmsg "/camup"
{
unsigned int streamid=1;
while(stream_idtaken(streamid)){++streamid;}
++streamcount;
streams=realloc(streams, sizeof(struct stream)*streamcount);
- streams[streamcount-1].userid=id;
+ streams[streamcount-1].camid=id;
streams[streamcount-1].streamid=streamid;
streams[streamcount-1].outgoing=1;
struct rtmp amf;
@@ -83,13 +82,11 @@ void stream_play(struct amf* amf, int sock) // called upon _result
if(streams[i].streamid==amf->items[2].number)
{
struct rtmp amf;
- amfinit(&amf, 8);
+ amfinit(&amf, streams[i].streamid*6+2);
amfstring(&amf, streams[i].outgoing?"publish":"play");
amfnum(&amf, 0);
amfnull(&amf);
- char camid[snprintf(0,0,"%u0", streams[i].userid)];
- sprintf(camid, "%u", streams[i].userid);
- amfstring(&amf, camid);
+ amfstring(&amf, streams[i].camid);
if(streams[i].outgoing){amfstring(&amf, "live");}
amf.msgid=le32(streams[i].streamid);
amfsend(&amf, sock);
@@ -118,9 +115,9 @@ void stream_handledata(struct rtmp* rtmp)
// fprintf(stderr, "Chunk: chunkid: %u, streamid: %u, userid: %u\n", rtmp->chunkid, rtmp->msgid, streams[i].userid);
if(rtmp->type==RTMP_VIDEO)
{
- printf("Video: %u %u\n", streams[i].userid, rtmp->length);
+ printf("Video: %s %u\n", streams[i].camid, rtmp->length);
}else if(rtmp->type==RTMP_AUDIO){
- printf("Audio: %u %u\n", streams[i].userid, rtmp->length);
+ printf("Audio: %s %u\n", streams[i].camid, rtmp->length);
}
fwrite(rtmp->buf, rtmp->length, 1, stdout);
fflush(stdout);
@@ -140,9 +137,8 @@ void stream_handlestatus(struct amf* amf, int sock)
if(code->type!=AMF_STRING || details->type!=AMF_STRING){return;}
if(!strcmp(code->string.string, "NetStream.Play.Stop"))
{
- unsigned int id=strtoul(details->string.string, 0, 0);
- printf("VideoEnd: %u\n", id);
- stream_stopvideo(sock, id);
+ printf("VideoEnd: %s\n", details->string.string);
+ stream_stopvideo(sock, details->string.string);
}
}
@@ -165,12 +161,12 @@ void stream_sendframe(int sock, void* buf, size_t len, unsigned char type)
}
}
-void stream_stopvideo(int sock, unsigned int id)
+void stream_stopvideo(int sock, const char* id)
{
unsigned int i;
for(i=0; i<streamcount; ++i)
{
- if(streams[i].userid==id)
+ if(!strcmp(streams[i].camid, id))
{
struct rtmp amf;
// Close the stream
@@ -187,6 +183,7 @@ void stream_stopvideo(int sock, unsigned int id)
amfnull(&amf);
amfnum(&amf, streams[i].streamid);
amfsend(&amf, sock);
+ free((void*)streams[i].camid);
// Remove from list of streams
--streamcount;
memmove(&streams[i], &streams[i+1], sizeof(struct stream)*(streamcount-i));
diff --git a/media.h b/media.h
index 329cb21..18c4f67 100644
--- a/media.h
+++ b/media.h
@@ -19,18 +19,18 @@
struct stream
{
unsigned int streamid;
- unsigned int userid;
+ const char* camid;
char outgoing;
};
extern struct stream* streams;
extern unsigned int streamcount;
-extern void stream_start(const char* nick, int sock); // called upon privmsg "/opencam ..."
-extern void streamout_start(unsigned int id, int sock); // called upon privmsg "/camup"
+extern void stream_start(const char* nick, const char* camid, int sock); // called upon privmsg "/opencam ..."
+extern void streamout_start(const char* id, int sock); // called upon privmsg "/camup"
extern void stream_play(struct amf* amf, int sock); // called upon _result
extern void stream_handledata(struct rtmp* rtmp);
extern void stream_handlestatus(struct amf* amf, int sock);
extern void stream_sendframe(int sock, void* buf, size_t len, unsigned char type);
-extern void stream_stopvideo(int sock, unsigned int id);
+extern void stream_stopvideo(int sock, const char* id);
extern void setallowsnapshots(int sock, char v);
diff --git a/testbuilds.sh b/testbuilds.sh
index 925f9a6..b6d473d 100755
--- a/testbuilds.sh
+++ b/testbuilds.sh
@@ -4,7 +4,7 @@ rm -rf testbuilds
mkdir -p testbuilds
cd testbuilds
-printf "Without mic support, with gtk+-2.x, without RTMP_DEBUG, without youtube support: "
+printf "Without mic support, with gtk+-2.x, without RTMP_DEBUG, without youtube support, with glib: "
res="[31mbroken[0m"
while true; do
../configure > /dev/null 2> /dev/null || break
@@ -14,6 +14,7 @@ while true; do
if ! grep -q 'GTK_LIBS=.*-lgtk-[^- ]*-2' config.mk; then res="gtk+-2.x not found, can't test"; break; fi
if ! grep -q '^AVCODEC_LIBS' config.mk; then res="libavcodec not found, can't test"; break; fi
if ! grep -q '^SWSCALE_LIBS' config.mk; then res="libswscale not found, can't test"; break; fi
+ if ! grep -q '^GLIB_LIBS' config.mk; then res="glib not found, can't test"; break; fi
sed -i -e '/^AO_LIBS/d' config.mk
sed -i -e '/^AVFORMAT_LIBS/d' config.mk
echo 'CFLAGS+=-Werror' >> config.mk
@@ -31,7 +32,7 @@ mv camviewer camviewer.gtk2 > /dev/null 2> /dev/null
mv tc_client-gtk tc_client-gtk.gtk2 > /dev/null 2> /dev/null
make clean > /dev/null 2> /dev/null
-printf "With mic support, with gtk+-3.x, with RTMP_DEBUG, with youtube support: "
+printf "With mic support, with gtk+-3.x, with RTMP_DEBUG, with youtube support, without glib: "
res="[31mbroken[0m"
while true; do
../configure > /dev/null 2> /dev/null || break
@@ -41,6 +42,7 @@ while true; do
if ! grep -q '^AVCODEC_LIBS' config.mk; then res="libavcodec not found, can't test"; break; fi
if ! grep -q '^SWSCALE_LIBS' config.mk; then res="libswscale not found, can't test"; break; fi
if ! grep -q '^AVFORMAT_LIBS' config.mk; then res="libavformat not found, can't test"; break; fi
+ sed -i -e '/GLIB_/d' config.mk
echo 'CFLAGS+=-DRTMP_DEBUG=1 -Werror' >> config.mk
if ! make utils > /dev/null 2> /dev/null; then
sed -i -e 's/-Werror[^ ]*//g' config.mk
diff --git a/tinychat.c b/tinychat.c
new file mode 100644
index 0000000..7ea0f29
--- /dev/null
+++ b/tinychat.c
@@ -0,0 +1,806 @@
+/*
+ tc_client, a simple non-flash client for tinychat(.com)
+ 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
+ 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include "client.h"
+#include "idlist.h"
+#include "rtmp.h"
+#include "colors.h"
+#include "amfwriter.h"
+#include "numlist.h"
+#include "media.h"
+#define sitearg (greenroom?"greenroom":"tinychat")
+static const char* bpassword=0;
+static char* unban=0;
+
+static char* gethost(const char *channel, const char *password)
+{
+ int urllen;
+ if(password)
+ {
+ urllen=strlen("http://tinychat.com/api/find.room/?site=&password=0")+strlen(channel)+strlen(sitearg)+strlen(password);
+ }else{
+ urllen=strlen("http://tinychat.com/api/find.room/?site=0")+strlen(channel)+strlen(sitearg);
+ }
+ char url[urllen];
+ if(password)
+ {
+ sprintf(url, "http://tinychat.com/api/find.room/%s?site=%s&password=%s", channel, sitearg, password);
+ }else{
+ sprintf(url, "http://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);}
+ if(strncmp(result, "OK", 2) && strncmp(result, "RES", 3)){printf("Result not OK\n"); exit(-1);}
+ // Find and extract the server responsible for this channel
+ char* rtmp=strstr(response, "rtmp='rtmp://");
+ if(!rtmp){printf("No rtmp found.\n"); exit(-1);}
+ rtmp+=strlen("rtmp='rtmp://");
+ 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");
+ }
+ char* end;
+ if(bpass && (end=strchr(&bpass[12], '\'')))
+ {
+ end[0]=0;
+ bpassword=strdup(&bpass[12]);
+ printf("Authenticated to broadcast\n");
+ }
+ free(response);
+ return host;
+}
+
+static char* getkey(int id, const char* channel)
+{
+ char url[snprintf(0,0, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%i&room=%s%%5E%s", id, sitearg, channel)+1];
+ sprintf(url, "http://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\":\"");
+
+ if(!key){return 0;}
+ key+=7;
+ char* keyend=strchr(key, '"');
+ if(!keyend){return 0;}
+ char* backslash;
+ while((backslash=strchr(key, '\\')))
+ {
+ memmove(backslash, &backslash[1], strlen(backslash));
+ --keyend;
+ }
+ key=strndup(key, keyend-key);
+ free(response);
+ return key;
+}
+
+static void getcaptcha(void)
+{
+ char* url="http://tinychat.com/cauth/captcha";
+ char* page=http_get(url, 0);
+ char* token=strstr(page, "\"token\":\"");
+ if(token)
+ {
+ token=&token[9];
+ char* end=strchr(token, '"');
+ if(end)
+ {
+ end[0]=0;
+ printf("Captcha: http://tinychat.com/cauth/recaptcha?token=%s\n", token);
+ fflush(stdout);
+ fgetc(stdin);
+ }
+ }
+ free(page);
+}
+
+static char* getcookie(const char* channel)
+{
+ unsigned long long now=time(0);
+ char url[strlen("http://tinychat.com/cauth?t=&room=0")+snprintf(0,0, "%llu", now)+strlen(channel)];
+ sprintf(url, "http://tinychat.com/cauth?t=%llu&room=%s", now, channel);
+ char* response=http_get(url, 0);
+ if(strstr(response, "\"lurker\":1"))
+ {
+ printf("Captcha not completed, you will not be able to send any messages or broadcast\n");
+ }
+ char* cookie=strstr(response, "\"cookie\":\"");
+
+ if(!cookie){return 0;}
+ cookie+=10;
+ char* end=strchr(cookie, '"');
+ if(!end){return 0;}
+ cookie=strndup(cookie, end-cookie);
+ free(response);
+ return cookie;
+}
+
+static char* getbroadcastkey(const char* channel, const char* nick, const char* bpassword)
+{
+ unsigned int id=idlist_get(nick);
+ char url[snprintf(0,0, "http://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://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){free(response); return 0;}
+ key+=8;
+ char* keyend=strchr(key, '\'');
+ if(!keyend){free(response); return 0;}
+ key=strndup(key, keyend-key);
+ free(response);
+ return key;
+}
+
+static char* getmodkey(const char* user, const char* pass, const char* channel, char* loggedin)
+{
+ // TODO: if possible, do this in a neater way than digging the key out from an HTML page.
+ if(!user||!pass){return 0;}
+ char post[strlen("form_sent=1&username=&password=&next=http://tinychat.com/0")+strlen(user)+strlen(pass)+strlen(channel)];
+ sprintf(post, "form_sent=1&username=%s&password=%s&next=http://tinychat.com/%s", user, pass, channel);
+ char* response=http_get("http://tinychat.com/login", post);
+ char* key=strstr(response, "autoop: \"");
+ if(key)
+ {
+ key=&key[9];
+ char* end=strchr(key, '"');
+ if(end)
+ {
+ end[0]=0;
+ key=strdup(key);
+ }else{key=0;}
+ }
+ if(strstr(response, "<div class='name'")){*loggedin=1;}
+ free(response);
+ return key;
+}
+
+static char checknick(const char* nick) // Returns zero if the nick is valid, otherwise returning the character that failed the check
+{
+ unsigned int i;
+ for(i=0; nick[i]; ++i)
+ {
+ if(!strchr("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-^[]{}`\\|", nick[i])){return nick[i];}
+ }
+ return 0;
+}
+
+static void sendmessage_priv(int sock, 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];
+ sprintf(privfield, "n%i-%s", id, priv?priv:"");
+
+ struct rtmp amf;
+ char* msg=tonumlist((char*)buf, strlen(buf));
+ amfinit(&amf, 3);
+ amfstring(&amf, "privmsg");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, msg);
+ 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(priv)
+ {
+ amfstring(&amf, privfield);
+ // And one in case they're broadcasting
+ privfield[0]='b'; // 'b' for broadcasting
+ struct rtmp bamf;
+ amfinit(&bamf, 3);
+ amfstring(&bamf, "privmsg");
+ amfnum(&bamf, 0);
+ amfnull(&bamf);
+ amfstring(&bamf, msg);
+ amfstring(&bamf, colors[currentcolor%COLORCOUNT]);
+ amfstring(&bamf, privfield);
+ amfsend(&bamf, sock);
+ }
+ amfsend(&amf, sock);
+ free(msg);
+}
+
+static void sendmessage(int sock, const char* buf)
+{
+ if(!strncmp(buf, "/priv ", 6))
+ {
+ const char* msg=strchr(&buf[6], ' ');
+ if(msg)
+ {
+ char priv[msg-&buf[6]+1];
+ memcpy(priv, &buf[6], msg-&buf[6]);
+ priv[msg-&buf[6]]=0;
+ sendmessage_priv(sock, &msg[1], priv);
+ return;
+ }
+ }
+ sendmessage_priv(sock, buf, 0);
+}
+
+static void sendpm(int sock, 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);
+}
+
+static void changenick(int sock, const char* newnick)
+{
+ char badchar;
+ if((badchar=checknick(newnick)))
+ {
+ printf("'%c' is not allowed in nicknames.\n", badchar);
+ return;
+ }
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "nick");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, newnick);
+ amfsend(&amf, sock);
+}
+
+static void joinreg(struct amf* amfin, int sock)
+{
+ if(amf_comparestrings_c(&amfin->items[0].string, "joins"))
+ {
+ printf("Currently online: ");
+ }else{
+ printf("%s ", timestamp());
+ }
+ int i;
+ for(i = 2; i < amfin->itemcount; ++i)
+ {
+ if(amfin->items[i].type==AMF_OBJECT)
+ {
+ struct amfitem* item=amf_getobjmember(&amfin->items[i].object, "id");
+ if(item->type!=AMF_NUMBER){continue;}
+ int id=item->number;
+ item=amf_getobjmember(&amfin->items[i].object, "nick");
+ if(item->type!=AMF_STRING){continue;}
+ const char* nick=item->string.string;
+ item=amf_getobjmember(&amfin->items[i].object, "account");
+ if(item->type!=AMF_STRING){continue;}
+ const char* account=(item->string.string[0]?item->string.string:0);
+ item=amf_getobjmember(&amfin->items[i].object, "mod");
+ if(item->type!=AMF_BOOL){continue;}
+ char mod=item->boolean;
+ idlist_add(id, nick, account, mod);
+ printf("%s%s", (i==2?"":", "), nick);
+ if(amf_comparestrings_c(&amfin->items[0].string, "registered"))
+ {
+ // Tell the server how often to let us know it got our packets
+ struct rtmp setbw={
+ .type=RTMP_SERVER_BW,
+ .chunkid=2,
+ .length=4,
+ .msgid=0,
+ .buf="\x00\x00\x40\x00" // Every 0x4000 bytes
+ };
+ rtmp_send(sock, &setbw);
+ char* key=getkey(id, channel);
+ if(!key){printf("Failed to get channel key\n"); exit(1);}
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "cauth");
+ amfnum(&amf, 0);
+ amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
+ amfstring(&amf, key);
+ amfsend(&amf, sock);
+ free(key);
+ // Set the given nickname
+ changenick(sock, nickname);
+ // Keep what the server gave us, just in case
+ free(nickname);
+ nickname=strdup(nick);
+ }
+ }
+ }
+ if(amf_comparestrings_c(&amfin->items[0].string, "joins"))
+ {
+ printf("\n");
+ for(i=0; i<idlistlen; ++i)
+ {
+ if(idlist[i].op){printf("%s is a moderator.\n", idlist[i].name);}
+ }
+ printf("Currently on cam: ");
+ char first=1;
+ for(i = 2; i < amfin->itemcount; ++i)
+ {
+ if(amfin->items[i].type==AMF_OBJECT)
+ {
+ struct amfitem* item=amf_getobjmember(&amfin->items[i].object, "bf");
+ if(item->type!=AMF_BOOL || !item->boolean){continue;}
+ item=amf_getobjmember(&amfin->items[i].object, "nick");
+ if(item->type!=AMF_STRING){continue;}
+ printf("%s%s", (first?"":", "), item->string.string);
+ first=0;
+ }
+ }
+ printf("\n");
+ }else{
+ printf(" entered the channel\n");
+ if(idlist[idlistlen-1].op){printf("%s is a moderator.\n", idlist[idlistlen-1].name);}
+ if(amf_comparestrings_c(&amfin->items[0].string, "registered"))
+ {
+ printf("Connection ID: %i\n", idlist[0].id);
+ }
+ }
+}
+
+static void privmsg(struct amf* amfin, int sock)
+{
+ if(amfin->items[3].type!=AMF_STRING || amfin->items[4].type!=AMF_STRING || amfin->items[5].type!=AMF_STRING){return;}
+ size_t len;
+ char* msg=fromnumlist(amfin->items[3].string.string, &len);
+ const char* color=color_start(amfin->items[4].string.string);
+ char* line=msg;
+ while(line)
+ {
+ // Handle multi-line messages
+ char* nextline=0;
+ unsigned int linelen;
+ for(linelen=0; &line[linelen]<&msg[len]; ++linelen)
+ {
+ if(line[linelen]=='\r' || line[linelen]=='\n'){nextline=&line[linelen+1]; break;}
+ }
+ printf("%s %s%s: ", timestamp(), color, amfin->items[5].string.string);
+ fwrite(line, linelen, 1, stdout);
+ printf("%s\n", color_end());
+ line=nextline;
+ }
+ if(!strncmp(msg, "/allowbroadcast ", 16))
+ {
+ if(getbroadcastkey(channel, nickname, &msg[16])) // Validate password
+ {
+ free((void*)bpassword);
+ bpassword=strdup(&msg[16]);
+ printf("Authenticated to broadcast\n");
+ }
+ }
+ else if(!strcmp(msg, "/version"))
+ {
+ sendmessage_priv(sock, "/version tc_client-" VERSION, amfin->items[5].string.string);
+ }
+ free(msg);
+}
+
+static void userquit(struct amf* amfin, int sock)
+{
+ // quit/part
+ if(amfin->items[2].type!=AMF_STRING){return;}
+ idlist_remove(amfin->items[2].string.string);
+ printf("%s %s left the channel\n", timestamp(), amfin->items[2].string.string);
+}
+
+static void usernick(struct amf* amfin, int sock)
+{
+ // nick
+ if(amfin->items[2].type!=AMF_STRING || amfin->items[3].type!=AMF_STRING){return;}
+ if(!strcmp(amfin->items[2].string.string, nickname)) // Successfully changed our own nickname
+ {
+ free(nickname);
+ nickname=strdup(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);
+}
+
+static void userkick(struct amf* amfin, int sock)
+{
+ // kick
+ if(amfin->items[2].type!=AMF_STRING){return;}
+ if(atoi(amfin->items[2].string.string) == idlist_get(nickname))
+ {
+ printf("%s You have been kicked out\n", timestamp());
+ }
+}
+
+static void banned(struct amf* amfin, int sock)
+{
+ // banned
+ printf("%s You are banned from %s\n", timestamp(), channel);
+ exit(1); // Getting banned is a failure, right?
+}
+
+static void closecam(int sock, 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);
+}
+
+static void fromowner(struct amf* amfin, int sock)
+{
+ // from_owner: notices, mute, push2talk, closing cams
+ if(amfin->items[2].type!=AMF_STRING){return;}
+ 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);
+ }
+ else if(!strcmp("mute", amfin->items[2].string.string))
+ {
+ printf("%s Non-moderators have been temporarily muted.\n", timestamp());
+ }
+ else if(!strcmp("push2talk", amfin->items[2].string.string))
+ {
+ printf("%s Push to talk request has been sent to non-moderators.\n", timestamp());
+ }
+ 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);
+ }
+}
+
+static void nickinuse(struct amf* amfin, int sock)
+{
+ // nickinuse, the nick we wanted to change to is already taken
+ printf("Nick is already in use.\n");
+}
+
+static void channeltopic(struct amf* amfin, int sock)
+{
+ // Room topic
+ if(amfin->items[2].type!=AMF_STRING || !amfin->items[2].string.length){return;}
+ printf("Room topic: %s\n", amfin->items[2].string.string);
+}
+
+static void banlist(struct amf* amfin, int sock)
+{
+ // Get list of banned users
+ unsigned int i;
+ if(unban) // This is not a response to /banlist but to /forgive
+ {
+ // If the nickname isn't found in the banlist we assume it's an ID
+ const char* id=unban;
+ for(i=2; i+1<amfin->itemcount; i+=2)
+ {
+ if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
+ if(!strcmp(amfin->items[i+1].string.string, unban) || !strcmp(amfin->items[i].string.string, unban))
+ {
+ id=amfin->items[i].string.string;
+ }
+ }
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "forgive");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, id);
+ amfsend(&amf, sock);
+ free(unban);
+ unban=0;
+ return;
+ }
+ printf("Banned users:\n");
+ printf("ID Nickname\n");
+ for(i=2; i+1<amfin->itemcount; i+=2)
+ {
+ if(amfin->items[i].type!=AMF_STRING || amfin->items[i+1].type!=AMF_STRING){break;}
+ unsigned int len=printf("%s", amfin->items[i].string.string);
+ for(;len<10; ++len){printf(" ");}
+ printf(" %s\n", amfin->items[i+1].string.string);
+ }
+ printf("Use /forgive <ID/nick> to unban someone\n");
+}
+
+static void notice(struct amf* amfin, int sock)
+{
+ if(amfin->items[2].type!=AMF_STRING || !amf_comparestrings_c(&amfin->items[2].string, "avon") || amfin->items[4].type!=AMF_STRING){return;}
+ printf("%s cammed up\n", amfin->items[4].string.string);
+}
+
+static void result(struct amf* amfin, int sock)
+{
+ // Handle results for various requests, haven't seen much of a pattern to it, always successful?
+ // Creating a new stream worked, now play media (cam/mic) on it (if that's what the result was for)
+ stream_play(amfin, sock);
+}
+
+static void onstatus(struct amf* amfin, int sock)
+{
+ stream_handlestatus(amfin, sock);
+}
+
+static void mod_close(int sock, const char* nick)
+{
+ struct rtmp amf;
+ amfinit(&amf, 2);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ char buf[strlen("closed: 0")+strlen(nick)];
+ sprintf(buf, "_close%s", nick);
+ amfstring(&amf, buf);
+ amfsend(&amf, sock);
+ sprintf(buf, "closed: %s", nick);
+ sendmessage(sock, buf);
+}
+
+static void mod_ban(int sock, const char* nick)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+// char buf[strlen("notice%20was%20banned%20by%20%20()0")+strlen(nick)+strlen(nickname)+strlen(account_user)];
+// sprintf(buf, "notice%s%%20was%%20banned%%20by%%20%s%%20(%s)", nick, nickname, account_user);
+ 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);
+// 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)
+ amfinit(&amf, 3);
+ amfstring(&amf, "kick");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, nick);
+ sprintf(buf, "%i", idlist_get(nick));
+ amfstring(&amf, buf);
+ amfsend(&amf, sock);
+}
+
+static void mod_banlist(int sock)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "banlist");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfsend(&amf, sock);
+}
+
+static void mod_unban(int sock, const char* nick)
+{
+ free(unban);
+ unban=strdup(nick);
+ mod_banlist(sock);
+}
+
+static void mod_mute(int sock)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, "mute");
+ amfsend(&amf, sock);
+}
+
+static void mod_push2talk(int sock)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, "push2talk");
+ amfsend(&amf, sock);
+}
+
+static void mod_topic(int sock, const char* newtopic)
+{
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "topic");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, newtopic);
+ amfstring(&amf, "");
+ amfsend(&amf, sock);
+}
+
+static void mod_allowbroadcast(int sock, const char* nick)
+{
+ if(!bpassword){return;}
+ char buf[strlen("/allowbroadcast 0")+strlen(bpassword)];
+ sprintf(buf, "/allowbroadcast %s", bpassword);
+ sendmessage_priv(sock, buf, nick);
+}
+
+static void camup(int sock)
+{
+ // Retrieve and send the key for broadcasting access
+ char* key=getbroadcastkey(channel, nickname, bpassword);
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "bauth");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, key);
+ amfsend(&amf, sock);
+ 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);
+}
+
+static void camdown(int sock)
+{
+ unsigned int userid=idlist_get(nickname);
+ char camid[snprintf(0,0,"%u", userid)+1];
+ sprintf(camid, "%u", userid);
+ stream_stopvideo(sock, camid);
+}
+
+static void opencam(int sock, 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);
+}
+
+int init_tinychat(const char* chanpass, const char* username, const char* userpass, struct site* site)
+{
+ char badchar;
+ if((badchar=checknick(nickname)))
+ {
+ printf("'%c' is not allowed in nicknames.\n", badchar);
+ return -1;
+ }
+ registercmd("privmsg", privmsg, 6);
+ registercmd("join", joinreg, 3);
+ registercmd("nick", usernick, 5);
+ registercmd("quit", userquit, 3);
+ registercmd("notice", notice, 5);
+ registercmd("from_owner", fromowner, 3);
+ registercmd("nickinuse", nickinuse, 1);
+ registercmd("banlist", banlist, 1);
+ registercmd("topic", channeltopic, 3);
+ registercmd("joins", joinreg, 3);
+ registercmd("registered", joinreg, 3);
+ registercmd("onStatus", onstatus, 1);
+ registercmd("_result", result, 3);
+ registercmd("kick", userkick, 4);
+ registercmd("banned", banned, 2);
+ site->sendmessage=sendmessage;
+ site->sendpm=sendpm;
+ site->nick=changenick;
+ site->mod_close=mod_close;
+ site->mod_ban=mod_ban;
+ site->mod_banlist=mod_banlist;
+ site->mod_unban=mod_unban;
+ site->mod_mute=mod_mute;
+ site->mod_push2talk=mod_push2talk;
+ site->mod_topic=mod_topic;
+ site->mod_allowbroadcast=mod_allowbroadcast;
+ site->opencam=opencam;
+ site->closecam=closecam;
+ site->camup=camup;
+ site->camdown=camdown;
+ char loggedin=0;
+ // Log in if user account is specified
+ char* modkey=getmodkey(username, userpass, channel, &loggedin);
+ char* server=gethost(channel, chanpass);
+ // Separate IP/domain and port
+ char* port=strchr(server, ':');
+ if(!port){return 3;}
+ port[0]=0;
+ ++port;
+ int sock=connectto(server, port);
+
+ rtmp_handshake(sock);
+ if(!loggedin){username=0; userpass=0;}
+ getcaptcha();
+ char* cookie=getcookie(channel);
+ // Send connect request
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "connect");
+ amfnum(&amf, 0);
+ amfobjstart(&amf);
+ amfobjitem(&amf, "app");
+ {char str[strlen("chat/0")+strlen(channel)];
+ sprintf(str, "chat/%s", channel);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "flashVer");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "swfUrl");
+ amfstring(&amf, "no flash");
+
+ amfobjitem(&amf, "tcUrl");
+ {char str[strlen("rtmp://:/tinyconf0")+strlen(server)+strlen(port)+strlen(channel)];
+ sprintf(str, "rtmp://%s:%s/chat/%s", server, port, channel);
+ amfstring(&amf, str);}
+
+ amfobjitem(&amf, "fpad");
+ amfbool(&amf, 0);
+
+ amfobjitem(&amf, "capabilities");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "audioCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoCodecs");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "videoFunction");
+ amfnum(&amf, 0);
+
+ amfobjitem(&amf, "pageUrl");
+ char pageurl[strlen("http://tinychat.com/rooms/0") + strlen(channel)];
+ sprintf(pageurl, "http://tinychat.com/rooms/%s", channel);
+ amfstring(&amf, pageurl);
+
+ amfobjitem(&amf, "objectEncoding");
+ amfnum(&amf, 0);
+ amfobjend(&amf);
+ amfobjstart(&amf);
+ amfobjitem(&amf, "version");
+ amfstring(&amf, "Desktop");
+
+ amfobjitem(&amf, "type");
+ amfstring(&amf, "default"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to, but "default" seems to work fine too.
+
+ amfobjitem(&amf, "account");
+ amfstring(&amf, username?username:"");
+
+ amfobjitem(&amf, "prefix");
+ amfstring(&amf, sitearg);
+
+ amfobjitem(&amf, "room");
+ amfstring(&amf, channel);
+
+ if(modkey)
+ {
+ amfobjitem(&amf, "autoop");
+ amfstring(&amf, modkey);
+ }
+ amfobjitem(&amf, "cookie");
+ amfstring(&amf, cookie);
+ amfobjend(&amf);
+ amfsend(&amf, sock);
+ free(modkey);
+ free(cookie);
+ return sock;
+}
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index c91a67b..64ab1cc 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -255,6 +255,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
if(config_get_bool("disablesnapshots")){write(tc_client_in[1], "/disablesnapshots\n", 18);}
write(tc_client_in[1], "/color\n", 7); // Check which random color tc_client picked
unsigned int length=strlen(&buf[15]);
+ free(nickname);
nickname=malloc(length+strlen("guest-")+1);
sprintf(nickname, "guest-%s", &(buf[15]));
if(hasgreenroom){greenroom_join(&buf[15]);}
@@ -895,6 +896,8 @@ void startsession(GtkButton* button, void* x)
gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordwindow")));
GtkWidget* mainwindow=GTK_WIDGET(gtk_builder_get_object(gui, "main"));
const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")));
+ free(nickname);
+ nickname=strdup(nick);
channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")));
const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "channelpassword")));
if(!chanpass[0]){chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_password")));}