$ git clone https://tcclient.ion.nu/tc_client.git
commit 7591a17c892567c5f205cf69bf6c4bd5ab007b93
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.20
diff --git a/ChangeLog b/ChangeLog
index cd2abe0..4662717 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+0.20:
+Fixed amf0 reading of numbers (type 0)
+Added support for sending format 0 RTMP packets (which include the msgid field)
+Handle "/userinfo $request", responding with the account name if logged in and otherwise "tc_client" (which is not a valid tinychat account name but the flash client will show it anyway)
+Added a /priv command, mostly for use by modbot.
+Improved the way RTMP payloads are read from the stream.
+modbot: when someone joins, send a /mbs (start video) command with the current position in the video, but only to that user.
0.19:
Got unicode characters working, as it turns out tinychat doesn't use ISO-8859-1 but UTF-16 (which shares character codes with ISO-8859-1 below 256)
Enable keepalive on the TCP socket.
diff --git a/Makefile b/Makefile
index 105b5d1..b364d79 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.19
+VERSION=0.20
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
ifneq ($(wildcard config.mk),)
diff --git a/README b/README
index 4c84504..73890b7 100644
--- a/README
+++ b/README
@@ -8,6 +8,7 @@ Commands supported by tc_client:
/color <0-15> = set the color of your outgoing messages
/colors = list the available colors
/nick <newnick> = change nickname
+/help = list these commands at runtime
Some things that will probably never change:
*tc_client can't view people's webcams or listen to mics
diff --git a/amfparser.c b/amfparser.c
index 3e0cf1b..8dfaf51 100644
--- a/amfparser.c
+++ b/amfparser.c
@@ -97,6 +97,8 @@ struct amf* amf_parse(const unsigned char* buf, int len)
item=amf_newitem(amf);
item->type=AMF_NUMBER;
memcpy(&item->number, buf, sizeof(double));
+ unsigned long long* endian=(void*)&item->number;
+ *endian=be64(*endian);
buf=&buf[sizeof(double)];
break;
case 1:
diff --git a/amfwriter.c b/amfwriter.c
index 97876ec..76d06d2 100644
--- a/amfwriter.c
+++ b/amfwriter.c
@@ -27,11 +27,14 @@ void amfinit(struct rtmp* msg, unsigned int streamid)
msg->type=RTMP_AMF0;
msg->streamid=streamid;
msg->length=0;
+ msg->msgid=0;
msg->buf=0;
}
void amfnum(struct rtmp* msg, double v)
{
+ unsigned long long* endian=(void*)&v;
+ *endian=be64(*endian);
int offset=msg->length;
msg->length+=1+sizeof(double);
msg->buf=realloc(msg->buf, msg->length);
diff --git a/client.c b/client.c
index 7df618d..f2e8bcc 100644
--- a/client.c
+++ b/client.c
@@ -141,7 +141,7 @@ char* getkey(char* id, char* channel)
return key;
}
-char* getmodkey(const char* user, const char* pass, const char* channel)
+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;}
@@ -159,6 +159,7 @@ char* getmodkey(const char* user, const char* pass, const char* channel)
key=strdup(key);
}else{key=0;}
}
+ if(strstr(response, "<div class='name'")){*loggedin=1;}
free(response);
return key;
}
@@ -183,6 +184,25 @@ char checknick(const char* nick) // Returns zero if the nick is valid, otherwise
return 0;
}
+char* getprivfield(char* nick)
+{
+ unsigned int id;
+ unsigned int privlen;
+ for(privlen=0; nick[privlen]&&nick[privlen]!=' '; ++privlen);
+ id=idlist_get((char*)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);
+ strncat(priv, nick, privlen);
+ return priv;
+}
+
void usage(const char* me)
{
printf("Usage: %s [options] <channelname> <nickname> [channelpassword]\n"
@@ -214,7 +234,7 @@ int main(int argc, char** argv)
else if(!strcmp(argv[i], "-p")||!strcmp(argv[i], "--pass"))
{
++i;
- account_pass=argv[i];
+ account_pass=strdup(argv[i]);
}
else if(!channel){channel=argv[i];}
else if(!nickname){nickname=strdup(argv[i]);}
@@ -273,9 +293,11 @@ int main(int argc, char** argv)
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};
+ struct rtmp rtmp={0,0,0,0,0};
// Handshake complete, log in (if user account is specified)
- char* modkey=getmodkey(account_user, account_pass, channel);
+ char loggedin=0;
+ char* modkey=getmodkey(account_user, account_pass, channel, &loggedin);
+ if(!loggedin){free(account_pass); account_user=0; account_pass=0;}
// Send connect request
struct rtmp amf;
amfinit(&amf, 3);
@@ -354,8 +376,7 @@ 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;
- int privfield=-1;
- int privlen;
+ char* privfield=0;
if(buf[0]=='/') // Got a command
{
if(!strcmp((char*)buf, "/help"))
@@ -410,16 +431,32 @@ int main(int argc, char** argv)
}
else if(!strncmp((char*)buf, "/msg ", 5))
{
- for(privlen=0; buf[5+privlen]&&buf[5+privlen]!=' '; ++privlen);
- privfield=idlist_get((char*)&buf[5]);
- if(privfield<0)
- {
- buf[5+privlen]=0;
- printf("No such nick: %s\n", &buf[5]);
- fflush(stdout);
- continue;
- }
+ privfield=getprivfield((char*)&buf[5]);
+ if(!privfield){continue;}
+ }
+ else if(!strncmp((char*)buf, "/priv ", 6))
+ {
+ char* end=strchr((char*)&buf[6], ' ');
+ if(!end){continue;}
+ privfield=getprivfield((char*)&buf[6]);
+ if(!privfield){continue;}
+ len=strlen(&end[1]);
+ memmove(buf, &end[1], len+1);
}
+/* While we can get the server to send us video data, we don't know how to handle the data yet.
+ else if(!strncmp((char*)buf, "/cam ", 5))
+ {
+ unsigned int id=idlist_get((char*)&buf[5]);
+ camid=malloc(128);
+ sprintf(camid, "%u", id);
+ amfinit(&amf, 3);
+ amfstring(&amf, "createStream");
+ amfnum(&amf, 2);
+ amfnull(&amf);
+ amfsend(&amf, sock);
+ continue;
+ }
+*/
}
char* msg=tonumlist((char*)buf, len);
amfinit(&amf, 3);
@@ -429,12 +466,10 @@ int main(int argc, char** argv)
amfstring(&amf, msg);
amfstring(&amf, colors[currentcolor%16]);
// For PMs, add a string like "n<numeric ID>-<nick>" to make the server only send it to the intended recipient
- if(privfield>-1)
+ if(privfield)
{
- char priv[snprintf(0, 0, "n%i-", privfield)+privlen+1];
- sprintf(priv, "n%i-", privfield);
- strncat(priv, (char*)&buf[5], privlen);
- amfstring(&amf, priv);
+ amfstring(&amf, privfield);
+ free(privfield);
}
amfsend(&amf, sock);
free(msg);
@@ -445,6 +480,13 @@ int main(int argc, char** argv)
// Read the RTMP stream and handle AMF0 packets
if(rtmp_get(sock, &rtmp))
{
+/* Getting video/cam data, but we don't know how to make use of it yet
+ if(rtmp.type==RTMP_VIDEO)
+ {
+ write(vidf, rtmp.buf, rtmp.length);
+ continue;
+ }
+*/
if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); continue;}
struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING)
@@ -483,6 +525,31 @@ int main(int argc, char** argv)
printf("%s \x1b[%sm%s: ", timestamp(), color, amfin->items[5].string.string);
fwrite(msg, len, 1, stdout);
printf("\x1b[0m\n");
+ if(len==18 && !strncmp(msg, "/userinfo $request", 18))
+ {
+ char* msg;
+ if(account_user)
+ {
+ unsigned int len=strlen("/userinfo \n0")+strlen(account_user);
+ char buf[len+1];
+ sprintf(buf, "/userinfo %s\n", account_user);
+ msg=tonumlist(buf, len);
+ }else{
+ msg=tonumlist("/userinfo tc_client\n", 20); // TODO: include version number?
+ }
+ amfinit(&amf, 3);
+ amfstring(&amf, "privmsg");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, msg);
+ 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);
+ free(msg);
+ }
free(msg);
fflush(stdout);
}
@@ -590,6 +657,22 @@ int main(int argc, char** argv)
printf("Room topic: %s\n", amfin->items[2].string.string);
fflush(stdout);
}
+/* More not yet usable cam code, response to trying to open a new stream
+ else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "_result"))
+ {
+ printamf(amfin);
+ if(camid)
+ {
+ amfinit(&amf, 8);
+ amfstring(&amf, "play");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, camid);
+ amf.msgid=le32(1);
+ amfsend(&amf, sock);
+ }
+ }
+*/
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
diff --git a/endian.c b/endian.c
index c327ab4..17f9a2c 100644
--- a/endian.c
+++ b/endian.c
@@ -16,6 +16,22 @@
*/
#include "endian.h"
+unsigned long long be64(unsigned long long in)
+{
+#if(__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)
+ return ((in&0xff)<<56) |
+ ((in&0xff00)<<40) |
+ ((in&0xff0000)<<24) |
+ ((in&0xff000000)<<8) |
+ ((in&0xff00000000)>>8) |
+ ((in&0xff0000000000)>>24) |
+ ((in&0xff000000000000)>>40) |
+ ((in&0xff00000000000000)>>56);
+#else
+ return in;
+#endif
+}
+
unsigned long be32(unsigned long in)
{
#if(__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)
diff --git a/endian.h b/endian.h
index 790416d..1f87c60 100644
--- a/endian.h
+++ b/endian.h
@@ -14,6 +14,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+extern unsigned long long be64(unsigned long long in);
extern unsigned long be32(unsigned long in);
extern unsigned long le32(unsigned long in);
extern unsigned short be16(unsigned short in);
diff --git a/modbot.c b/modbot.c
index d6b8dde..5cffbb9 100644
--- a/modbot.c
+++ b/modbot.c
@@ -24,6 +24,7 @@
#include <signal.h>
#include <sys/wait.h>
#include <stdarg.h>
+#include <time.h>
struct list
{
@@ -36,6 +37,7 @@ struct list queue={0,0};
struct list goodvids={0,0}; // pre-approved videos
struct list badvids={0,0}; // not allowed, essentially banned
char* playing=0;
+time_t started=0;
int tc_client;
void list_del(struct list* list, const char* item)
@@ -222,6 +224,7 @@ void playnextvid()
say(0, "/mbs youTube %s 0\n", playing);
// Find out the video's length and schedule an alarm for then
alarm(getduration(playing));
+ started=time(0);
}
void playnext(int x)
@@ -444,7 +447,7 @@ int main(int argc, char** argv)
{
if(playing)
{
- if(list_contains(&goodvids, playing) && !list_contains(&badvids, playing)){say(pm, "'%s' is already approved, use !approve <ID> to approve another video\n", playing); continue;}
+ if(list_contains(&goodvids, playing) && !list_contains(&badvids, playing)){say(pm, "'%s' is already approved, use !approve <ID> to approve another video (or 'next' instead of an ID to approve the next not-yet-approved video in queue)\n", playing); continue;}
list_add(&goodvids, playing);
list_del(&badvids, playing);
list_save(&goodvids, "goodvids.txt");
@@ -527,14 +530,18 @@ int main(int argc, char** argv)
list_save(&goodvids, "goodvids.txt");
free(playing);
playing=strdup(vid);
- alarm(getduration(vid));
+ unsigned int pos=(end?(strtol(&end[1], 0, 0)/1000):0);
+ alarm(getduration(playing)-pos);
+ started=time(0)-pos;
}
else if(!strcmp(msg, "/mbc youTube")){playnext(0);} // Video cancelled
else if(!strncmp(msg, "/mbsk youTube ", 14)) // Seeking
{
unsigned int pos=strtol(&msg[14], 0, 0)/1000;
alarm(getduration(playing)-pos);
+ started=time(0)-pos;
}
+ // TODO: handle /mbpa (pause) and /mbpl (resume play)
}
}else{ // Actions
if(!strncmp(space, " changed nickname to ", 21))
@@ -556,6 +563,14 @@ int main(int argc, char** argv)
}
continue;
}
+ else if(!strcmp(space, " entered the channel")) // Newcomer, inform about the currently playing video
+ {
+ if(playing)
+ {
+ space[0]=0;
+ say(0, "/priv %s /mbs youTube %s %u\n", nick, playing, (time(0)-started)*1000);
+ }
+ }
}
}
}
diff --git a/rtmp.c b/rtmp.c
index 34261ee..a1f5ac8 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -31,6 +31,7 @@ char rtmp_get(int sock, struct rtmp* rtmp)
if(read(sock, &x, 1)<1){return 0;}
unsigned int streamid=x&0x3f;
unsigned int fmt=(x&0xc0)>>6;
+ unsigned int msgid=0;
// Handle extended stream IDs
if(streamid<2) // (0=1 extra byte, 1=2 extra bytes)
{
@@ -52,8 +53,7 @@ char rtmp_get(int sock, struct rtmp* rtmp)
read(sock, &type, sizeof(type));
if(fmt<1)
{
- // Message ID (is this ever used?)
- unsigned int msgid;
+ // Message ID
read(sock, &msgid, sizeof(msgid));
}
}
@@ -67,16 +67,19 @@ char rtmp_get(int sock, struct rtmp* rtmp)
rtmp->type=type;
rtmp->streamid=streamid;
- rtmp->length=length+length/128;
+ rtmp->length=length;
+ rtmp->msgid=le32(msgid);
free(rtmp->buf);
rtmp->buf=malloc(rtmp->length);
- read(sock, rtmp->buf, rtmp->length);
- int i;
- for(i=128; i<rtmp->length; i+=128)
+ size_t pos=0;
+ size_t w;
+ // Only read up to 128 bytes at a time and discard the (garbage/RTMP continuation header) bytes in between
+ while(pos<length)
{
- --rtmp->length;
- // printf("Dropping garbage byte %x\n", (unsigned int)((unsigned char*)rtmp->buf)[i]&0xff);
- memmove(rtmp->buf+i, rtmp->buf+i+1, rtmp->length-i);
+ w=read(sock, rtmp->buf+pos, ((length-pos>127)?128:(length-pos)));
+ if(w<1){break;}
+ pos+=w;
+ if(length-pos>0){read(sock, &w, 1);} // Skip junk once every 128 bytes
}
return 1;
}
@@ -84,7 +87,8 @@ char rtmp_get(int sock, struct rtmp* rtmp)
void rtmp_send(int sock, struct rtmp* rtmp)
{
// Header format and stream ID
- unsigned char basicheader=(rtmp->streamid<64?rtmp->streamid:(rtmp->streamid<256?0:1)) | (1<<6);
+ unsigned int fmt=(rtmp->msgid?0:1);
+ unsigned char basicheader=(rtmp->streamid<64?rtmp->streamid:(rtmp->streamid<256?0:1)) | (fmt<<6);
write(sock, &basicheader, sizeof(basicheader));
if(rtmp->streamid>=64) // Handle large stream IDs
{
@@ -105,7 +109,10 @@ void rtmp_send(int sock, struct rtmp* rtmp)
write(sock, ((void*)&x)+1, 3);
// Type
write(sock, &rtmp->type, sizeof(rtmp->type));
- // TODO: is there a situation where message IDs are needed? (format 0 header)
+ if(fmt<1) // Send message ID if there is one (that isn't 0)
+ {
+ write(sock, &rtmp->msgid, sizeof(rtmp->msgid));
+ }
// Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
void* pos=rtmp->buf;
unsigned int len=rtmp->length;
diff --git a/rtmp.h b/rtmp.h
index 3326286..f18b2ce 100644
--- a/rtmp.h
+++ b/rtmp.h
@@ -29,6 +29,7 @@ struct rtmp
unsigned char type;
unsigned int streamid;
unsigned int length;
+ unsigned int msgid;
void* buf;
};