$ git clone http://tcclient.ion.nu/tc_client.git
commit ffa4f152ddb7380d4accf273488c6bcd660bde60
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.14
diff --git a/ChangeLog b/ChangeLog
index 166407b..aedeef8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+0.14:
+Use iconv to deal with tinychat's ISO-8859-1 instead of the wcs functions.
+irchack: only listen on 127.0.0.1 (localhost)
+Added -u/--user and -p/--pass options to log into tinychat accounts.
0.13:
Check the validity of nicknames before switching.
Roll our own endianness conversion functions since the ones provided by libc are not standardized.
diff --git a/Makefile b/Makefile
index 2c4e561..57e0b58 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.13
+VERSION=0.14
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
diff --git a/README b/README
index 897560f..7987c95 100644
--- a/README
+++ b/README
@@ -1,7 +1,6 @@
Some notes about tc_client in its current state (things that should be fixed):
*there is no real user interface, to send a message you just type it and it might get cut off by incoming messages, it'll be ugly. There is however the irchack program that lets you use an IRC client.
*PMs can be sent by /msg <nickname> <message> and replies appear similarly.
-*you can't log in with an account and thus not do mod stuff, I don't know how this works protocol-wise
Some things that will probably never change:
*tc_client can't view people's webcams or listen to mics
diff --git a/client.c b/client.c
index a1ae3da..7005e21 100644
--- a/client.c
+++ b/client.c
@@ -62,7 +62,7 @@ size_t writehttp(char* ptr, size_t size, size_t nmemb, void* x)
return size;
}
-char* http_get(char* url)
+char* http_get(const char* url, const char* post)
{
CURL* curl=curl_easy_init();
if(!curl){return 0;}
@@ -70,6 +70,10 @@ char* http_get(char* url)
struct writebuf writebuf={0, 0};
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writehttp);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writebuf);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla Firefox");
+ if(post){curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);}
char err[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
if(curl_easy_perform(curl)){curl_easy_cleanup(curl); printf(err); return 0;}
@@ -93,7 +97,7 @@ char* gethost(char *channel, char *password)
}else{
sprintf(url, "http://tinychat.com/api/find.room/%s?site=tinychat", channel);
}
- char *response=http_get(url);
+ char *response=http_get(url, 0);
//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); return 0;}
@@ -116,7 +120,7 @@ char* getkey(char* id, char* channel)
{
char url[strlen("http://tinychat.com/api/captcha/check.php?guest%5Fid=&room=tinychat%5E0")+strlen(id)+strlen(channel)];
sprintf(url, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%s&room=tinychat%%5E%s", id, channel);
- char* response=http_get(url);
+ char* response=http_get(url, 0);
char* key=strstr(response, "\"key\":\"");
if(!key){return 0;}
@@ -134,6 +138,28 @@ char* getkey(char* id, char* channel)
return key;
}
+char* getmodkey(const char* user, const char* pass, const char* channel)
+{
+ // 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;}
+ }
+ free(response);
+ return key;
+}
+
char timestampbuf[8];
char* timestamp()
{
@@ -154,23 +180,50 @@ char checknick(const char* nick) // Returns zero if the nick is valid, otherwise
return 0;
}
+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"
+ "-u, --user <user> Username of tinychat account to use.\n"
+ "-p, --pass <pass> Password of tinychat account to use.\n"
+ ,me);
+}
+
int main(int argc, char** argv)
{
- setlocale(LC_ALL, "");
- if(argc<3)
- {
- printf("Usage: %s <channelname> <nickname> [password]\n", argv[0]);
- return 1;
+ char* channel=0;
+ char* nickname=0;
+ char* password=0;
+ char* account_user=0;
+ char* account_pass=0;
+ int i;
+ for(i=1; i<argc; ++i)
+ {
+ if(!strcmp(argv[i], "-h")||!strcmp(argv[i], "--help")){usage(argv[0]); return 0;}
+ else if(!strcmp(argv[i], "-u")||!strcmp(argv[i], "--user"))
+ {
+ ++i;
+ account_user=argv[i];
+ }
+ else if(!strcmp(argv[i], "-p")||!strcmp(argv[i], "--pass"))
+ {
+ ++i;
+ account_pass=argv[i];
+ }
+ else if(!channel){channel=argv[i];}
+ else if(!nickname){nickname=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(argv[2])))
{
printf("'%c' is not allowed in nicknames.\n", badchar);
return 1;
}
- char* channel=argv[1];
- char* nickname=strdup(argv[2]);
- char* password=(argc>3?argv[3]:0);
+ setlocale(LC_ALL, "");
char* server=gethost(channel, password);
struct addrinfo* res;
// Separate IP/domain and port
@@ -202,7 +255,9 @@ int main(int argc, char** argv)
b_read(sock, handshake, 1536); // Read our junk back, we don't bother checking that it's the same
printf("Handshake complete\n");
struct rtmp rtmp={0,0,0};
- // Handshake complete, send connect request
+ // Handshake complete, log in (if user account is specified)
+ char* modkey=getmodkey(account_user, account_pass, channel);
+ // Send connect request
struct rtmp amf;
amfinit(&amf, 3);
amfstring(&amf, "connect");
@@ -246,11 +301,12 @@ int main(int argc, char** argv)
amfnum(&amf, 0);
amfobjend(&amf);
amfstring(&amf, channel);
- amfstring(&amf, "none");
+ amfstring(&amf, modkey?modkey:"none");
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, "");
+ amfstring(&amf, modkey?account_user:"");
amfsend(&amf, sock);
+ free(modkey);
struct pollfd pfd[2];
pfd[0].fd=0;
@@ -327,10 +383,7 @@ int main(int argc, char** argv)
}
}
}
- len=mbstowcs(0, (char*)buf, 0);
- wchar_t wcsbuf[len+1];
- mbstowcs(wcsbuf, (char*)buf, len+1);
- char* msg=tonumlist(wcsbuf);
+ char* msg=tonumlist((char*)buf, len);
amfinit(&amf, 3);
amfstring(&amf, "privmsg");
amfnum(&amf, 0);
@@ -386,13 +439,12 @@ int main(int argc, char** argv)
// Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
else 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)
{
- wchar_t* msg=fromnumlist(amfin->items[3].string.string);
+ size_t len;
+ char* msg=fromnumlist(amfin->items[3].string.string, &len);
const char* color=(showcolor?resolvecolor(amfin->items[4].string.string):"0");
-#ifndef __ANDROID__
- printf("%s \x1b[%sm%s: %ls\x1b[0m\n", timestamp(), color, amfin->items[5].string.string, msg);
-#else // Wide characters are broken on android
- printf("%s \x1b[%sm%s: %s\x1b[0m\n", timestamp(), color, amfin->items[5].string.string, msg);
-#endif
+ printf("%s \x1b[%sm%s: ", timestamp(), color, amfin->items[5].string.string);
+ fwrite(msg, len, 1, stdout);
+ printf("\x1b[0m\n");
free(msg);
fflush(stdout);
}
@@ -491,7 +543,7 @@ int main(int argc, char** argv)
// 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);
+ printf("Nick is already in use.\n");
fflush(stdout);
}
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
diff --git a/irchack.c b/irchack.c
index 67a45dc..e376f0b 100644
--- a/irchack.c
+++ b/irchack.c
@@ -1,6 +1,6 @@
/*
irchack, a simple application to reuse IRC clients as user interfaces for tc_client
- Copyright (C) 2014 alicia@ion.nu
+ Copyright (C) 2014-2015 alicia@ion.nu
Copyright (C) 2015 Jade Lea
This program is free software: you can redistribute it and/or modify
@@ -105,7 +105,7 @@ int main(int argc, char** argv)
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
- addr.sin_addr.s_addr=0;
+ addr.sin_addr.s_addr=htonl(0x7f000001); // 127.0.0.1
addr.sin_port=htons(port);
int lsock=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(bind(lsock, (struct sockaddr*)&addr, sizeof(addr))){perror("bind"); return 1;}
diff --git a/numlist.c b/numlist.c
index b1806a2..5051d26 100644
--- a/numlist.c
+++ b/numlist.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
@@ -17,22 +17,21 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
-#include <wchar.h>
-#include <locale.h>
+#include <iconv.h>
#include "numlist.h"
// Functions for converting to/from the comma-separated decimal character code format that tinychat uses for chat messages, e.g. "97,98,99" = "abc"
-wchar_t* fromnumlist(char* in)
+char* fromnumlist(char* in, size_t* outlen)
{
- int len=1;
+ size_t len=1;
char* x=in;
while((x=strchr(x, ',')))
{
++len;
x=&x[1];
}
- unsigned char string[len+1];
+ char string[len+1];
int i;
for(i=0; i<len; ++i)
{
@@ -41,22 +40,30 @@ wchar_t* fromnumlist(char* in)
in=&in[1];
}
string[len]=0;
- wchar_t* wstring=malloc(sizeof(wchar_t)*(len+1));
- setlocale(LC_ALL, "en_US.ISO-8859-1");
- mbstowcs(wstring, (char*)string, len);
- wstring[len]=0;
- setlocale(LC_ALL, "");
- return wstring;
+
+ iconv_t cd=iconv_open("", "iso-8859-1");
+ char* outbuf=malloc(len*4);
+ char* i_out=outbuf;
+ char* i_in=string;
+ size_t remaining=len*4;
+ *outlen=remaining;
+ while(outlen>0 && len>0 && iconv(cd, &i_in, &len, &i_out, &remaining)>0);
+ i_out[0]=0; // null-terminates 'outbuf'
+ iconv_close(cd);
+ *outlen-=remaining;
+ return outbuf;
}
-char* tonumlist(const wchar_t* in_x)
+char* tonumlist(char* i_in, size_t len)
{
- int len=wcslen(in_x);
+ iconv_t cd=iconv_open("iso-8859-1", "");
char in[len+1];
- setlocale(LC_ALL, "en_US.ISO-8859-1");
- wcstombs(in, in_x, len);
- in[len]=0;
- setlocale(LC_ALL, "");
+ char* i_out=in;
+ size_t outlen=len;
+ while(outlen>0 && len>0 && iconv(cd, &i_in, &len, &i_out, &outlen)>0);
+ i_out[0]=0; // null-terminates the 'in' buffer
+ iconv_close(cd);
+
char* out=malloc(strlen(in)*strlen("255,"));
out[0]=0;
char* x=out;
diff --git a/numlist.h b/numlist.h
index 963daff..7144bef 100644
--- a/numlist.h
+++ b/numlist.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
@@ -14,12 +14,5 @@
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/>.
*/
-#ifdef __ANDROID__ // Compatibility hacks for android
- #define wchar_t char
- #define mbstowcs(dst,src,len) ((dst)?(int)strncpy(dst,src,len):strlen(src))
- #define wcstombs strncpy
- #define wcslen strlen
-#endif
-
-extern wchar_t* fromnumlist(char* in);
-extern char* tonumlist(const wchar_t* in);
+extern char* fromnumlist(char* in, size_t* outlen);
+extern char* tonumlist(char* in_x, size_t len);