$ git clone http://tcclient.ion.nu/tc_client.git
commit 00f823523b6565fece30ecc8d60d8ebb7e7bbb96
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.12
diff --git a/ChangeLog b/ChangeLog
index 93f7f26..f2b31d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+0.12:
+Wrote a sender for the RTMP code and changed the AMF0 writer to use it, plus working directly with the rtmp struct instead of an intermediate amfmsg struct.
+Added handling of moderators turning into regular users, or 'deop' (contributed by Jade)
+Handle new nicknames being in use when using /nick, and thus failing (contributed by Jade)
0.11:
Rewrote the RTMP code to read from the socket instead of from a buffer, this will prevent messages from being dropped when they are larger than the buffer.
0.10:
diff --git a/Makefile b/Makefile
index 71d3434..a96e224 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.11
+VERSION=0.12
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
diff --git a/amfwriter.c b/amfwriter.c
index 2011c8f..dd51863 100644
--- a/amfwriter.c
+++ b/amfwriter.c
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2014-2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -19,87 +19,43 @@
#include <stdint.h>
#include <unistd.h>
#include <endian.h>
+#include "rtmp.h"
#include "amfwriter.h"
-#pragma pack(push)
-#pragma pack(1)
-struct rtmph
+void amfinit(struct rtmp* msg, unsigned int streamid)
{
- unsigned int streamid:6;
- unsigned int fmt:2;
- unsigned int timestamp:24;
- unsigned int length:24;
- unsigned char type;
- unsigned int msgid;
-};
-#pragma pack(pop)
-
-extern unsigned int flip(unsigned int bits, int bytecount);
-
-void amfinit(struct amfmsg* msg)
-{
- msg->len=0;
- msg->buf=malloc(sizeof(struct rtmph));
-}
-
-void amfsend(struct amfmsg* msg, int sock)
-{
- struct rtmph* head=(struct rtmph*)msg->buf;
- head->streamid=3;
- head->fmt=0;
- head->timestamp=0; // Time is irrelevant
- head->length=flip(msg->len, 3);
- head->type=20;
- head->msgid=0;
- // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
-// printf("Writing %i-byte message\n", msg->len);
- write(sock, msg->buf, sizeof(struct rtmph));
- unsigned char* pos=msg->buf+sizeof(struct rtmph);
- while(msg->len>0)
- {
- int w;
- if(msg->len>128)
- {
- w=write(sock, pos, 128);
- w+=write(sock, "\xc3", 1);
- msg->len-=128;
- }else{
- w=write(sock, pos, msg->len);
- msg->len=0;
- }
-// printf("Wrote %i bytes\n", w);
- pos+=128;
- }
- free(msg->buf);
+ msg->type=RTMP_AMF0;
+ msg->streamid=streamid;
+ msg->length=0;
msg->buf=0;
}
-void amfnum(struct amfmsg* msg, double v)
+void amfnum(struct rtmp* msg, double v)
{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1+sizeof(double);
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=1+sizeof(double);
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* type=msg->buf+offset;
type[0]='\x00';
memcpy(msg->buf+offset+1, &v, sizeof(v));
}
-void amfbool(struct amfmsg* msg, char v)
+void amfbool(struct rtmp* msg, char v)
{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=2;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=2;
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* x=msg->buf+offset;
x[0]='\x01';
x[1]=!!v;
}
-void amfstring(struct amfmsg* msg, const char* string)
+void amfstring(struct rtmp* msg, const char* string)
{
int len=strlen(string);
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=3+len;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=3+len;
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* type=msg->buf+offset;
type[0]='\x02';
uint16_t* strlength=(uint16_t*)(msg->buf+offset+1);
@@ -107,41 +63,41 @@ void amfstring(struct amfmsg* msg, const char* string)
memcpy(msg->buf+offset+3, string, len);
}
-void amfobjstart(struct amfmsg* msg)
+void amfobjstart(struct rtmp* msg)
{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=1;
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* type=msg->buf+offset;
type[0]='\x03';
}
-void amfobjitem(struct amfmsg* msg, char* name)
+void amfobjitem(struct rtmp* msg, char* name)
{
int len=strlen(name);
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=2+len;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=2+len;
+ msg->buf=realloc(msg->buf, msg->length);
uint16_t* strlength=(uint16_t*)(msg->buf+offset);
*strlength=htobe16(len);
memcpy(msg->buf+offset+2, name, len);
}
-void amfobjend(struct amfmsg* msg)
+void amfobjend(struct rtmp* msg)
{
amfobjitem(msg, "");
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=1;
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* type=msg->buf+offset;
type[0]='\x09';
}
-void amfnull(struct amfmsg* msg)
+void amfnull(struct rtmp* msg)
{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
+ int offset=msg->length;
+ msg->length+=1;
+ msg->buf=realloc(msg->buf, msg->length);
unsigned char* type=msg->buf+offset;
type[0]='\x05';
}
diff --git a/amfwriter.h b/amfwriter.h
index 3828ad0..62f735e 100644
--- a/amfwriter.h
+++ b/amfwriter.h
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2014-2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -15,18 +15,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-struct amfmsg
-{
- unsigned int len;
- unsigned char* buf;
-};
+extern void amfinit(struct rtmp* msg, unsigned int streamid);
+extern void amfnum(struct rtmp* msg, double v);
+extern void amfbool(struct rtmp* msg, char v);
+extern void amfstring(struct rtmp* msg, const char* string);
+extern void amfobjstart(struct rtmp* msg);
+extern void amfobjitem(struct rtmp* msg, char* name);
+extern void amfobjend(struct rtmp* msg);
+extern void amfnull(struct rtmp* msg);
-extern void amfinit(struct amfmsg* msg);
-extern void amfsend(struct amfmsg* msg, int sock);
-extern void amfnum(struct amfmsg* msg, double v);
-extern void amfbool(struct amfmsg* msg, char v);
-extern void amfstring(struct amfmsg* msg, const char* string);
-extern void amfobjstart(struct amfmsg* msg);
-extern void amfobjitem(struct amfmsg* msg, char* name);
-extern void amfobjend(struct amfmsg* msg);
-extern void amfnull(struct amfmsg* msg);
+#define amfsend(rtmp,sock) rtmp_send(sock,rtmp); free((rtmp)->buf); (rtmp)->buf=0
diff --git a/client.c b/client.c
index 811965a..c0fb10c 100644
--- a/client.c
+++ b/client.c
@@ -1,7 +1,7 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014 alicia@ion.nu
- Copyright (C) 2014 Jade Lea
+ Copyright (C) 2014-2015 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
@@ -198,10 +198,10 @@ 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");
- // Handshake complete, send connect request
struct rtmp rtmp={0,0,0};
- struct amfmsg amf;
- amfinit(&amf);
+ // Handshake complete, send connect request
+ struct rtmp amf;
+ amfinit(&amf, 3);
amfstring(&amf, "connect");
amfnum(&amf, 0);
amfobjstart(&amf);
@@ -244,21 +244,11 @@ int main(int argc, char** argv)
amfobjend(&amf);
amfstring(&amf, channel);
amfstring(&amf, "none");
- amfstring(&amf, "show"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to
+ 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.
amfstring(&amf, "tinychat");
amfstring(&amf, "");
amfsend(&amf, sock);
- unsigned char buf[2048];
- int len=read(sock, buf, 2048);
-// printf("Received %i byte response\n", len);
-/* Debugging
- int f=open("output", O_WRONLY|O_CREAT|O_TRUNC, 0644);
- write(f, buf, len);
- close(f);
-*/
-
-// int outnum=2; (Debugging, number for output filenames)
struct pollfd pfd[2];
pfd[0].fd=0;
pfd[0].events=POLLIN;
@@ -273,7 +263,8 @@ int main(int argc, char** argv)
if(pfd[0].revents) // Got input, send a privmsg command
{
pfd[0].revents=0;
- len=read(0, buf, 2047);
+ unsigned char buf[2048];
+ unsigned int len=read(0, buf, 2047);
if(len<1){break;}
while(len>0 && (buf[len-1]=='\n'||buf[len-1]=='\r')){--len;}
if(!len){continue;} // Don't send empty lines
@@ -308,13 +299,11 @@ int main(int argc, char** argv)
}
else if(!strncmp((char*)buf, "/nick ", 6))
{
- free(nickname);
- nickname=strdup((char*)&buf[6]);
- amfinit(&amf);
+ amfinit(&amf, 3);
amfstring(&amf, "nick");
amfnum(&amf, 0);
amfnull(&amf);
- amfstring(&amf, nickname);
+ amfstring(&amf, (char*)&buf[6]);
amfsend(&amf, sock);
continue;
}
@@ -334,7 +323,7 @@ int main(int argc, char** argv)
wchar_t wcsbuf[len+1];
mbstowcs(wcsbuf, (char*)buf, len+1);
char* msg=tonumlist(wcsbuf);
- amfinit(&amf);
+ amfinit(&amf, 3);
amfstring(&amf, "privmsg");
amfnum(&amf, 0);
amfnull(&amf);
@@ -371,7 +360,7 @@ int main(int argc, char** argv)
printf("Guest ID: %s\n", id);
char* key=getkey(id, channel);
- amfinit(&amf);
+ amfinit(&amf, 3);
amfstring(&amf, "cauth");
amfnum(&amf, 0);
amfnull(&amf); // Means nothing but is apparently critically important for cauth at least
@@ -379,7 +368,7 @@ int main(int argc, char** argv)
amfsend(&amf, sock);
free(key);
- amfinit(&amf);
+ amfinit(&amf, 3);
amfstring(&amf, "nick");
amfnum(&amf, 0);
amfnull(&amf);
@@ -433,6 +422,11 @@ int main(int argc, char** argv)
// 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)
{
+ 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);
fflush(stdout);
@@ -475,15 +469,27 @@ int main(int argc, char** argv)
// oper, identifies mods
else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "oper") && amfin->items[3].type==AMF_STRING)
{
- idlist_set_op(amfin->items[3].string.string);
+ idlist_set_op(amfin->items[3].string.string, 1);
printf("%s is a moderator.\n", amfin->items[3].string.string);
fflush(stdout);
}
+ // deop, removes moderator privilege
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "deop") && amfin->items[3].type==AMF_STRING)
+ {
+ idlist_set_op(amfin->items[3].string.string, 0);
+ printf("%s is no longer a moderator.\n", amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // 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 %s is already in use.\n", nickname);
+ fflush(stdout);
+ }
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
else{printf("Server disconnected\n"); break;}
-// ++outnum; (Debugging)
}
free(rtmp.buf);
close(sock);
diff --git a/idlist.c b/idlist.c
index c7fe7a3..8cc0e16 100644
--- a/idlist.c
+++ b/idlist.c
@@ -1,6 +1,7 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2014-2015 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
@@ -74,14 +75,14 @@ int idlist_get(const char* name)
return -1;
}
-void idlist_set_op(const char* name)
+void idlist_set_op(const char* name, char op)
{
int i;
for(i=0; i<idlistlen; ++i)
{
if(!strcmp(name, idlist[i].name))
{
- idlist[i].op=1;
+ idlist[i].op=op;
return;
}
}
diff --git a/idlist.h b/idlist.h
index c08ab8d..b7c7f81 100644
--- a/idlist.h
+++ b/idlist.h
@@ -1,6 +1,7 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
Copyright (C) 2014 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
@@ -28,5 +29,5 @@ extern void idlist_add(int id, const char* name);
extern void idlist_remove(const char* name);
extern void idlist_rename(const char* oldname, const char* newname);
extern int idlist_get(const char* name);
-extern void idlist_set_op(const char* name);
+extern void idlist_set_op(const char* name, char op);
extern char idlist_is_op(const char* name);
diff --git a/irchack.c b/irchack.c
index c13fa8b..67a45dc 100644
--- a/irchack.c
+++ b/irchack.c
@@ -1,6 +1,7 @@
/*
irchack, a simple application to reuse IRC clients as user interfaces for tc_client
Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 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
@@ -252,6 +253,12 @@ printf("Got from tc_client: '%s'\n", buf);
dprintf(sock, ":irchack MODE #%s +o %s\n", channel, buf);
continue;
}
+ if(space && !strcmp(space, " is no longer a moderator."))
+ {
+ space[0]=0;
+ dprintf(sock, ":irchack MODE #%s -o %s\n", channel, buf);
+ continue;
+ }
if(buf[0]!='['){continue;} // Beyond this we only care about timestamped lines
// Translate ANSI escape codes to IRC color code instead
char* ansi;
diff --git a/rtmp.c b/rtmp.c
index bbb4da8..7ae7022 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -80,3 +80,48 @@ char rtmp_get(int sock, struct rtmp* rtmp)
}
return 1;
}
+
+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);
+ write(sock, &basicheader, sizeof(basicheader));
+ if(rtmp->streamid>=64) // Handle large stream IDs
+ {
+ if(rtmp->streamid<256)
+ {
+ unsigned char streamid=rtmp->streamid-64;
+ write(sock, &streamid, sizeof(streamid));
+ }else{
+ unsigned short streamid=htole16(rtmp->streamid-64);
+ write(sock, &streamid, sizeof(streamid));
+ }
+ }
+ unsigned int x=0;
+ // Timestamp
+ write(sock, &x, 3); // Time is irrelevant
+ // Length
+ x=htobe32(rtmp->length);
+ 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)
+ // 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;
+ while(len>0)
+ {
+ int w;
+ if(len>128)
+ {
+ w=write(sock, pos, 128);
+ w+=write(sock, "\xc3", 1);
+ len-=128;
+ }else{
+ w=write(sock, pos, len);
+ len=0;
+ }
+// printf("Wrote %i bytes\n", w);
+ pos+=128;
+ }
+}
diff --git a/rtmp.h b/rtmp.h
index b426079..3326286 100644
--- a/rtmp.h
+++ b/rtmp.h
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2015 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -33,3 +33,4 @@ struct rtmp
};
extern char rtmp_get(int sock, struct rtmp* rtmp);
+extern void rtmp_send(int sock, struct rtmp* rtmp);