$ git clone http://tcclient.ion.nu/tc_client.git
commit 75dd8cfa138cde86dbdb5139e15b735e5b980282
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.16
diff --git a/ChangeLog b/ChangeLog
index 6008164..dd44c65 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+0.16:
+Get and print the channel topic (contributed by Jade)
+irchack: fork and keep accepting connections.
+irchack: translate IRC's "\x01ACTION stuff\x01" (/me) into "*stuff*" for tc_client.
+modbot: add a 100ms delay between the lines of !help to prevent throttling.
+modbot: handle manual /mbs and /mbc commands (remove from queue, mark as good)
+Added a /help command to list commands handled by tc_client at runtime.
+irchack: use USER and PASS IRC commands to get the tinychat account to login as.
0.15:
Fixed a bug introduced with the new argument handling in 0.14
Prompt for password if a username is given with -u/--user but no password (-p/--pass) is given.
diff --git a/Makefile b/Makefile
index 23c0816..1a130b8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.15
+VERSION=0.16
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
diff --git a/README b/README
index 7987c95..4c84504 100644
--- a/README
+++ b/README
@@ -1,16 +1,25 @@
-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.
+tc_client is a very primitive application, it doesn't implement a user interface on its own,
+you could use it stand-alone but it will be ugly and if someone says something while you are typing a message your input-line will get cut off.
+Better interfaces can be implemented as wrappers that launch tc_client with stdin and stdout as pipes.
+
+Commands supported by tc_client:
+/msg <nickname> <message> = send a PM, incoming PMs look similar
+/color <on/off> = enable/disable showing colors with ANSI escapes
+/color <0-15> = set the color of your outgoing messages
+/colors = list the available colors
+/nick <newnick> = change nickname
Some things that will probably never change:
*tc_client can't view people's webcams or listen to mics
*tc_client can't stream/broadcast your webcam/mic
-*tc_client probably won't ever play youtube videos (although you can see when one is launched and get the video ID because it is sent as a message and can then open it yourself)
+*tc_client itself won't play youtube videos, but applications that rely on tc_client can interpret the /mbs, /mbsk and /mbc commands
Current commands sent by the TC servers that tc_client doesn't know how to handle:
notice
-topic
joinsdone
avons (list of people currently on cam)
pros
-oper
+
+Included applications that rely on tc_client (type 'make utils' to build):
+*irchack = a minimal IRC server that translates between IRC and tc_client's commands
+*modbot = a bot that handles youtube video requests with a queue and an approval system to keep inappropriate videos from being played. Supports the following commands: !help, !request, !queue (show queue status), for mods only: !playnext (try playing next in queue without marking it yet), !approve, !badvid (Note: modbot depends on youtube-dl to find out the length of videos)
diff --git a/client.c b/client.c
index c8cb173..bd724fb 100644
--- a/client.c
+++ b/client.c
@@ -348,7 +348,18 @@ int main(int argc, char** argv)
int privlen;
if(buf[0]=='/') // Got a command
{
- if(!strncmp((char*)buf, "/color", 6) && (!buf[6]||buf[6]==' '))
+ if(!strcmp((char*)buf, "/help"))
+ {
+ printf("/help = print this help text\n"
+ "/color <0-15> = pick color of your messages\n"
+ "/color <on/off> = turn on/off showing others' colors with ANSI codes\n"
+ "/color = see your current color\n"
+ "/colors = list the available colors and their numbers\n"
+ "/nick <newnick> = changes your nickname\n"
+ "/msg <to> <msg> = send a private message\n");
+ fflush(stdout);
+ }
+ else if(!strncmp((char*)buf, "/color", 6) && (!buf[6]||buf[6]==' '))
{
if(buf[6]) // Color specified
{
@@ -562,6 +573,12 @@ int main(int argc, char** argv)
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);
+ }
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
diff --git a/colors.c b/colors.c
index 1114e67..6812d67 100644
--- a/colors.c
+++ b/colors.c
@@ -18,7 +18,7 @@
#include "colors.h"
// Sorted like rainbows
-const char* colors[]={
+const char* colors[]={ // The 16 colors accepted by the flash client
"#821615,en",
"#c53332,en",
"#a08f23,en",
@@ -37,7 +37,7 @@ const char* colors[]={
"#b9807f,en"
};
-const char* termcolors[]={
+const char* termcolors[]={ // Equivalent color codes for ANSI escape sequences
"31",
"31;1",
"33",
diff --git a/irchack.c b/irchack.c
index e376f0b..26a67b9 100644
--- a/irchack.c
+++ b/irchack.c
@@ -23,6 +23,7 @@
#include <stdio.h>
#include <sys/socket.h>
#include <ctype.h>
+#include <signal.h>
#ifdef __ANDROID__
// Android has no dprintf, so we make our own
@@ -97,7 +98,7 @@ int findcolor_ansi(char* irc, char** end)
return -1;
}
-extern char session(int sock, const char* nick, const char* channel, const char* pass);
+extern char session(int sock, const char* nick, const char* channel, const char* pass, const char* acc_user, const char* acc_pass);
int main(int argc, char** argv)
{
@@ -111,24 +112,43 @@ int main(int argc, char** argv)
if(bind(lsock, (struct sockaddr*)&addr, sizeof(addr))){perror("bind"); return 1;}
listen(lsock, 1);
printf("Done! Open an IRC client and connect to localhost on port %i\n", port);
+ signal(SIGCHLD, SIG_IGN);
int sock;
while((sock=accept(lsock, 0, 0))>-1)
{
+ if(fork()){continue;}
char buf[2048];
char* nick=0;
char* channel=0;
char* pass=0;
+ char* acc_user=0;
+ char* acc_pass=0;
int len;
while(1)
{
len=0;
+ int r;
while(len<2047)
{
- if(read(sock, &buf[len], 1)!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
+ if((r=read(sock, &buf[len], 1))!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
++len;
}
+ if(r!=1){break;}
buf[len]=0;
- if(!strncmp(buf, "NICK ", 5))
+ if(!strncmp(buf, "USER ", 5))
+ {
+ acc_user=&buf[5];
+ char* end=strchr(acc_user, ' ');
+ if(end){end[0]=0;}
+ acc_user=strdup(acc_user);
+ }
+ else if(!strncmp(buf, "PASS ", 5))
+ {
+ acc_pass=&buf[5];
+ if(acc_pass[0]==':'){acc_pass=&acc_pass[1];}
+ acc_pass=strdup(acc_pass);
+ }
+ else if(!strncmp(buf, "NICK ", 5))
{
char* newnick=&buf[5];
if(newnick[0]==':'){newnick=&nick[1];}
@@ -156,7 +176,7 @@ int main(int argc, char** argv)
}
if(channel[0]=='#'){channel=&channel[1];}
channel=strdup(channel);
- if(!session(sock, nick, channel, pass)){break;}
+ if(!session(sock, nick, channel, pass, acc_user, acc_pass)){break;}
}
else if(!strncmp(buf, "PING ", 5))
{
@@ -164,12 +184,13 @@ int main(int argc, char** argv)
}
}
shutdown(sock, SHUT_RDWR);
+ _exit(0);
}
close(lsock);
return 0;
}
-char session(int sock, const char* nick, const char* channel, const char* pass)
+char session(int sock, const char* nick, const char* channel, const char* pass, const char* acc_user, const char* acc_pass)
{
printf("Nick: %s\n", nick);
printf("Channel: %s\n", channel);
@@ -184,7 +205,12 @@ char session(int sock, const char* nick, const char* channel, const char* pass)
close(tc_out[0]);
dup2(tc_in[0], 0);
dup2(tc_out[1], 1);
- execl("./tc_client", "./tc_client", channel, nick, pass, (char*)0);
+ if(acc_user && acc_pass)
+ {
+ execl("./tc_client", "./tc_client", "-u", acc_user, "-p", acc_pass, channel, nick, pass, (char*)0);
+ }else{
+ execl("./tc_client", "./tc_client", channel, nick, pass, (char*)0);
+ }
perror("Failed to exec tc_client");
_exit(1);
}
@@ -234,6 +260,11 @@ printf("Got from tc_client: '%s'\n", buf);
dprintf(sock, ":irchack 366 %s #%s :End of /NAMES list.\n", nick, channel);
joins=0;
}
+ if(!strncmp(buf, "Room topic: ", 12))
+ {
+ dprintf(sock, ":irchack 332 %s #%s :%s\n", nick, channel, &buf[12]);
+ continue;
+ }
if(!strcmp(buf, "Password required"))
{
dprintf(sock, ":irchack 475 %s :Cannot join %s without the correct password\n", channel, channel);
@@ -317,12 +348,13 @@ printf("Got from tc_client: '%s'\n", buf);
{
pfd[1].revents=0;
len=0;
+ int r;
while(len<2047)
{
- if(read(sock, &buf[len], 1)!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
+ if((r=read(sock, &buf[len], 1))!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
++len;
}
- if(len<=0){continue;}
+ if(r!=1){break;}
buf[len]=0;
printf("Got from IRC client: '%s'\n", buf);
if(!strncmp(buf, "PRIVMSG ", 8))
@@ -341,6 +373,13 @@ printf("Got from IRC client: '%s'\n", buf);
if(c!=-1){dprintf(tc_in[1], "/color %i\n", c);}
memmove(color, end, strlen(end)+1);
}
+ if(!strncmp(msg, "\x01""ACTION ", 8)) // Translate '/me'
+ {
+ msg=&msg[7];
+ msg[0]='*';
+ char* end=strchr(msg, '\x01');
+ if(end){end[0]='*';}
+ }
if(target[0]=='#' && !strcmp(&target[1], channel))
{
dprintf(tc_in[1], "%s\n", msg);
diff --git a/modbot.c b/modbot.c
index 69a54b4..d375590 100644
--- a/modbot.c
+++ b/modbot.c
@@ -157,13 +157,8 @@ void say(const char* pm, const char* fmt, ...)
write(tc_client, buf, strlen(buf));
}
-void playnextvid()
+unsigned int getduration(const char* vid)
{
- playing=queue.items[0];
- --queue.itemcount;
- memmove(queue.items, &queue.items[1], sizeof(char*)*queue.itemcount);
- say(0, "/mbs youTube %s 0\n", playing);
- // Find out the video's length and schedule an alarm for then
int out[2];
pipe(out);
if(!fork())
@@ -172,15 +167,15 @@ void playnextvid()
dup2(out[1], 1);
close(2); // Ignore youtube-dl errors/warnings
write(1, ":", 1);
- execlp("youtube-dl", "youtube-dl", "--get-duration", playing, (char*)0);
+ execlp("youtube-dl", "youtube-dl", "--get-duration", vid, (char*)0);
perror("execlp(youtube-dl)");
_exit(1);
}
- close(out[1]);
wait(0);
+ close(out[1]);
char timebuf[128];
int len=read(out[0], timebuf, 127);
- if(len<1){alarm(60); return;}
+ if(len<1){return 60;} // If using youtube-dl fails, assume all videos are 1 minute long
timebuf[len]=0;
close(out[0]);
// youtube-dl prints it out in hh:mm:ss format, convert it to plain seconds
@@ -196,11 +191,21 @@ void playnextvid()
// Days
sep=strrchr(timebuf, ':');
if(sep){sep[0]=0; len+=atoi(&sep[1])*24;}
-// printf("Estimated video length to %u sec\n", len);
- alarm(len);
+ return len;
}
char waitskip=0;
+void playnextvid()
+{
+ waitskip=0;
+ playing=queue.items[0];
+ --queue.itemcount;
+ memmove(queue.items, &queue.items[1], sizeof(char*)*queue.itemcount);
+ say(0, "/mbs youTube %s 0\n", playing);
+ // Find out the video's length and schedule an alarm for then
+ alarm(getduration(playing));
+}
+
void playnext(int x)
{
free(playing);
@@ -365,13 +370,21 @@ int main(int argc, char** argv)
else if(!strcmp(msg, "!help"))
{
say(nick, "The following commands can be used:\n");
+ usleep(100000);
say(nick, "!request <link> = request a video to be played\n");
+ usleep(100000);
say(nick, "!queue = get the number of songs in queue and which (if any) need to be approved\n");
+ usleep(100000);
say(nick, "Mod commands:\n"); // TODO: don't bother filling non-mods' chats with these?
+ usleep(100000);
say(nick, "!playnext = play the next video in queue without approving it (to see if it's ok)\n");
+ usleep(100000);
say(nick, "!approve = mark the currently playing video, or if none is playing the next in queue\n");
+ usleep(100000);
say(nick, "!approve <link> = mark the specified video as okay \n");
+ usleep(100000);
say(nick, "!badvid = stop playing the current video and mark it as bad\n");
+ usleep(100000);
say(nick, "!badvid <link> = mark the specified video as bad, preventing it from ever being queued again\n");
}
else if(list_contains(&mods, nick)) // Mods-only commands
@@ -417,6 +430,18 @@ int main(int argc, char** argv)
list_save(&badvids, "badvids.txt");
if(vid==playing){say(0, "/mbc youTube\n"); playnext(0);}
}
+ else if(!strncmp(msg, "/mbs youTube ", 13))
+ {
+ // Someone manually started a video, mark that video as good, remove it from queue, and set an alarm for when it's modbot's turn to play stuff again
+ char* vid=&msg[13];
+ char* end=strchr(vid, ' ');
+ if(end){end[0]=0;}
+ list_del(&queue, vid);
+ list_add(&goodvids, vid);
+ list_save(&goodvids, "goodvids.txt");
+ alarm(getduration(vid));
+ }
+ else if(!strcmp(msg, "/mbc youTube")){playnext(0);} // TODO: handle /mbsk (seek) too?
}
}else{ // Actions
if(!strncmp(space, " changed nickname to ", 21))