$ git clone http://tcclient.ion.nu/tc_client.git
commit a62abcf8901e85f41d232ddd4bf14b3f698e9b68
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.15
diff --git a/ChangeLog b/ChangeLog
index aedeef8..6008164 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+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.
+Turn account names lowercase to comply with tinychat's quirks.
+Handle networking being down at startup more graciously.
+Added modbot.c, a bot that deals with playing videos.
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)
diff --git a/Makefile b/Makefile
index 57e0b58..23c0816 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,14 @@
-VERSION=0.14
+VERSION=0.15
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
tc_client: client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
+utils: irchack modbot
+
clean:
- rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o tc_client
+ rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o tc_client irchack modbot
tarball:
- tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endian.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h LICENSE README ChangeLog crossbuild.sh irchack.c
+ tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c idlist.c colors.c endian.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h LICENSE README ChangeLog crossbuild.sh irchack.c modbot.c
diff --git a/client.c b/client.c
index 7005e21..c8cb173 100644
--- a/client.c
+++ b/client.c
@@ -25,6 +25,7 @@
#include <netdb.h>
#include <poll.h>
#include <locale.h>
+#include <ctype.h>
#include <curl/curl.h>
#include "rtmp.h"
#include "amfparser.h"
@@ -76,7 +77,7 @@ char* http_get(const char* url, const char* post)
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;}
+ if(curl_easy_perform(curl)){curl_easy_cleanup(curl); printf("%s\n", err); return 0;}
curl_easy_cleanup(curl);
return writebuf.buf; // should be free()d when no longer needed
}
@@ -97,17 +98,18 @@ char* gethost(char *channel, char *password)
}else{
sprintf(url, "http://tinychat.com/api/find.room/%s?site=tinychat", channel);
}
- char *response=http_get(url, 0);
+ 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); return 0;}
+ if(!result){printf("No result\n"); exit(-1);}
result+=strlen("result='");
// Handle the result value
- if(!strncmp(result, "PW", 2)){printf("Password required\n"); exit(-1); return 0;}
- if(strncmp(result, "OK", 2) && strncmp(result, "RES", 3)){printf("Result not OK\n"); exit(-1); return 0;}
+ 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); return 0;}
+ if(!rtmp){printf("No rtmp found.\n"); exit(-1);}
rtmp+=strlen("rtmp='rtmp://");
int len;
for(len=0; rtmp[len] && rtmp[len]!='/'; ++len);
@@ -205,6 +207,8 @@ int main(int argc, char** argv)
{
++i;
account_user=argv[i];
+ unsigned int j;
+ for(j=0; account_user[j]; ++j){account_user[j]=tolower(account_user[j]);}
}
else if(!strcmp(argv[i], "-p")||!strcmp(argv[i], "--pass"))
{
@@ -212,7 +216,7 @@ int main(int argc, char** argv)
account_pass=argv[i];
}
else if(!channel){channel=argv[i];}
- else if(!nickname){nickname=argv[i];}
+ else if(!nickname){nickname=strdup(argv[i]);}
else if(!password){password=argv[i];}
}
// Check for required arguments
@@ -224,6 +228,18 @@ int main(int argc, char** argv)
return 1;
}
setlocale(LC_ALL, "");
+ if(account_user && !account_pass) // Only username given, prompt for password
+ {
+ fprintf(stderr, "Account password: ");
+ fflush(stderr);
+ account_pass=malloc(128);
+ fgets(account_pass, 128, stdin);
+ unsigned int i;
+ for(i=0; account_pass[i]; ++i)
+ {
+ if(account_pass[i]=='\n'||account_pass[i]=='\r'){account_pass[i]=0; break;}
+ }
+ }
char* server=gethost(channel, password);
struct addrinfo* res;
// Separate IP/domain and port
diff --git a/crossbuild.sh b/crossbuild.sh
index 09b17b8..13c3d48 100755
--- a/crossbuild.sh
+++ b/crossbuild.sh
@@ -7,9 +7,9 @@ if [ "$host" = "" ]; then
fi
here="`pwd`"
if [ ! -e curlprefix ]; then
- wget -c http://curl.haxx.se/download/curl-7.39.0.tar.bz2
- tar -xjf curl-7.39.0.tar.bz2
- cd curl-7.39.0
+ wget -c http://curl.haxx.se/download/curl-7.40.0.tar.bz2
+ tar -xjf curl-7.40.0.tar.bz2
+ cd curl-7.40.0
mkdir -p build
cd build
../configure --prefix="${here}/curlprefix" --host="$host" --enable-static --disable-shared --disable-gopher --disable-ftp --disable-tftp --disable-ssh --disable-telnet --disable-dict --disable-file --disable-imap --disable-pop3 --disable-smtp --disable-ldap --without-librtmp --disable-rtsp --without-ssl --disable-sspi --without-nss --without-gnutls --without-libidn
@@ -27,4 +27,4 @@ elif which "${host}-cc" > /dev/null 2> /dev/null && [ "`which "${host}-cc" 2> /d
export CC="${host}-cc"
fi
make
-make irchack
+make utils
diff --git a/modbot.c b/modbot.c
index 5cffbb9..69a54b4 100644
--- a/modbot.c
+++ b/modbot.c
@@ -24,7 +24,6 @@
#include <signal.h>
#include <sys/wait.h>
#include <stdarg.h>
-#include <time.h>
struct list
{
@@ -37,43 +36,28 @@ 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)
{
unsigned int i;
- unsigned int len;
- while(item[0])
+ for(i=0; i<list->itemcount; ++i)
{
- if(item[0]=='\r' || item[0]=='\n'){item=&item[1]; continue;} // Skip empty lines
- for(len=0; item[len] && item[len]!='\r' && item[len]!='\n'; ++len);
- for(i=0; i<list->itemcount; ++i)
+ if(!strcmp(list->items[i], item))
{
- if(!strncmp(list->items[i], item, len) && !list->items[i][len])
- {
- free(list->items[i]);
- --list->itemcount;
- memmove(&list->items[i], &list->items[i+1], sizeof(char*)*(list->itemcount-i));
- }
+ free(list->items[i]);
+ --list->itemcount;
+ memmove(&list->items[i], &list->items[i+1], sizeof(char*)*list->itemcount);
}
- item=&item[len];
}
}
void list_add(struct list* list, const char* item)
{
list_del(list, item);
- unsigned int len;
- while(item[0])
- {
- if(item[0]=='\r' || item[0]=='\n'){item=&item[1]; continue;} // Skip empty lines
- for(len=0; item[len] && item[len]!='\r' && item[len]!='\n'; ++len);
- ++list->itemcount;
- list->items=realloc(list->items, sizeof(char*)*list->itemcount);
- list->items[list->itemcount-1]=strndup(item, len);
- item=&item[len];
- }
+ ++list->itemcount;
+ list->items=realloc(list->items, sizeof(char*)*list->itemcount);
+ list->items[list->itemcount-1]=strdup(item);
}
void list_switch(struct list* list, char* olditem, char* newitem)
@@ -92,16 +76,9 @@ void list_switch(struct list* list, char* olditem, char* newitem)
int list_getpos(struct list* list, char* item)
{
int i;
- unsigned int len;
- while(item[0])
+ for(i=0; i<list->itemcount; ++i)
{
- if(item[0]=='\r' || item[0]=='\n'){item=&item[1]; continue;} // Skip empty lines
- for(len=0; item[len] && item[len]!='\r' && item[len]!='\n'; ++len);
- for(i=0; i<list->itemcount; ++i)
- {
- if(!strncmp(list->items[i], item, len) && !list->items[i][len]){return i;}
- }
- item=&item[len];
+ if(!strcmp(list->items[i], item)){return i;}
}
return -1;
}
@@ -143,12 +120,22 @@ void list_save(struct list* list, const char* file)
close(f);
}
-void list_movetofront(struct list* list, unsigned int pos)
+void list_movetoback(struct list* list, const char* item)
+{
+ char tmp[strlen(item)+1];
+ strcpy(tmp, item);
+ list_del(list, tmp);
+ list_add(list, tmp);
+}
+
+char* getyoutube(char* string) // Extract youtube ID from URL/other forms
{
- if(pos>=list->itemcount){return;}
- char* move=list->items[pos];
- memmove(&list->items[1], list->items, sizeof(char*)*pos);
- list->items[0]=move;
+ char* x;
+ if((x=strstr(string, "?v=")))
+ {
+ return &x[3];
+ }
+ return string; // last resort: assume it's already an ID
}
void say(const char* pm, const char* fmt, ...)
@@ -170,35 +157,33 @@ void say(const char* pm, const char* fmt, ...)
write(tc_client, buf, strlen(buf));
}
-void getvidinfo(const char* vid, const char* type, char* buf, unsigned int len)
+void playnextvid()
{
+ 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())
{
close(out[0]);
dup2(out[1], 1);
- execlp("youtube-dl", "youtube-dl", "--default-search", "auto", type, "--", vid, (char*)0);
+ close(2); // Ignore youtube-dl errors/warnings
+ write(1, ":", 1);
+ execlp("youtube-dl", "youtube-dl", "--get-duration", playing, (char*)0);
perror("execlp(youtube-dl)");
_exit(1);
}
- wait(0);
close(out[1]);
- len=read(out[0], buf, len-1);
- if(len<0){len=0;}
- while(len>0 && (buf[len-1]=='\r' || buf[len-1]=='\n')){--len;} // Strip newlines
- buf[len]=0;
- close(out[0]);
-}
-
-unsigned int getduration(const char* vid)
-{
+ wait(0);
char timebuf[128];
- timebuf[0]=':'; // Sacrifice 1 byte to avoid having to deal with a special case later on, where no ':' is found and we go from the start of the string, but only once
- getvidinfo(vid, "--get-duration", &timebuf[1], 127);
- if(!timebuf[1]){printf("Failed to get video duration using youtube-dl, assuming 60s\n"); return 60;} // If using youtube-dl fails, assume videos are 1 minute long
+ int len=read(out[0], timebuf, 127);
+ if(len<1){alarm(60); return;}
+ timebuf[len]=0;
+ close(out[0]);
// youtube-dl prints it out in hh:mm:ss format, convert it to plain seconds
- unsigned int len;
// Seconds
char* sep=strrchr(timebuf, ':');
if(sep){sep[0]=0; len=atoi(&sep[1]);}
@@ -211,22 +196,11 @@ unsigned int getduration(const char* vid)
// Days
sep=strrchr(timebuf, ':');
if(sep){sep[0]=0; len+=atoi(&sep[1])*24;}
- return len;
-}
-
-unsigned int 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));
- started=time(0);
+// printf("Estimated video length to %u sec\n", len);
+ alarm(len);
}
+char waitskip=0;
void playnext(int x)
{
free(playing);
@@ -242,16 +216,17 @@ void playnext(int x)
{
if(list_contains(&goodvids, queue.items[i]))
{
- waitskip=i;
+ waitskip=1;
alarm(120);
break;
}
}
return;
}else{
- say(0, "Skipping http://youtube.com/watch?v=%s because it is still not approved after 2 minutes\n", queue.items[0]);
- list_movetofront(&queue, waitskip);
waitskip=0;
+ say(0, "Skipping http://youtube.com/watch?v=%s because it is still not approved after 2 minutes\n", queue.items[0]);
+ list_movetoback(&queue, queue.items[0]);
+ alarm(1);
}
}
playnextvid();
@@ -330,52 +305,33 @@ int main(int argc, char** argv)
}
if(!strncmp(msg, "!request ", 9))
{
- char title[256];
- char vid[1024];
- getvidinfo(&msg[9], "--get-id", vid, 1024);
- if(!vid[0]){say(pm, "No video found, sorry\n"); continue;} // Nothing found
- char* plist;
- for(plist=vid; plist[0] && plist[0]!='\r' && plist[0]!='\n'; plist=&plist[1]);
- if(plist[0]) // Link was a playlist, do some trickery to get the title of the first video (instead of getting nothing)
- {
- strcpy(title, "Playlist, starting with ");
- plist[0]=0;
- getvidinfo(vid, "--get-title", &title[24], 256-24);
- plist[0]='\n';
- }else{
- plist=0;
- getvidinfo(vid, "--get-title", title, 256);
- }
+ char* vid=getyoutube(&msg[9]);
printf("Requested ID '%s' by '%s'\n", vid, nick);
- // Check if it's already queued and mention which spot it's in, or if it's marked as bad and shouldn't be queued
+ // Check if it's already queued and mention which spot it's in
int pos;
if((pos=list_getpos(&queue, vid))>-1)
{
- say(pm, "Video '%s' is already in queue (number %i)\n", title, pos);
- continue;
+ say(pm, "That video is already in queue (number %i)\n", pos);
}
- if(list_contains(&badvids, vid))
- {
- say(pm, "Video '%s' is marked as bad, won't add to queue\n", title);
- continue;
- }
- if(list_contains(&mods, nick)) // Auto-approve for mods
+ if(list_contains(&mods, nick))
{
+// printf("is mod, video auto-approved\n");
list_add(&goodvids, vid);
list_del(&badvids, vid);
list_save(&goodvids, "goodvids.txt");
list_save(&badvids, "badvids.txt");
+ list_add(&queue, vid);
+ }else{ // Not a mod
+ if(list_contains(&badvids, vid))
+ {
+ write(tc_client, "Video is marked as bad, won't add to queue\n", 43);
+ continue;
+ }
+ list_add(&queue, vid);
}
-
- list_add(&queue, vid);
if(!list_contains(&goodvids, vid))
{
- if(plist)
- {
- say(pm, "Playlist '%s' is added to the queue but will need to be approved by a mod\n", title);
- }else{
- say(pm, "Video '%s' (%s) is added to the queue but will need to be approved by a mod\n", vid, title);
- }
+ say(pm, "Video '%s' is added to the queue but will need to be approved by mods\n", vid);
}
else if(!playing){playnext(0);}
else{say(pm, "Added to queue\n");}
@@ -406,34 +362,17 @@ int main(int argc, char** argv)
say(pm, "%u videos in queue\n", queue.itemcount);
}
}
- else if(!strcmp(msg, "!time")) // Debugging
- {
- unsigned int remaining=alarm(0);
- alarm(remaining);
- say(pm, "'%s' is scheduled to end in %u seconds\n", playing, remaining);
- }
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 as good, or if none is playing the next in queue\n");
- usleep(100000);
- say(nick, "!approve <link> = mark the specified video as okay\n");
- say(nick, "!approve next = mark the next not yet approved video as okay\n");
- say(nick, "!approve entire queue = approve all videos in queue (for playlists)\n");
- usleep(100000);
+ say(nick, "!approve = mark the currently playing video, or if none is playing the next in queue\n");
+ say(nick, "!approve <link> = mark the specified video as okay \n");
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");
- say(nick, "You can also just play videos manually and they will be marked as good.\n");
}
else if(list_contains(&mods, nick)) // Mods-only commands
{
@@ -447,101 +386,37 @@ 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 (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");
list_save(&badvids, "badvids.txt");
}else if(queue.itemcount>0){
- if(list_contains(&goodvids, queue.items[0]) && !list_contains(&badvids, queue.items[0])){say(pm, "'%s' is already approved, use !approve <ID> to approve another video\n", queue.items[0]); continue;}
list_add(&goodvids, queue.items[0]);
list_del(&badvids, queue.items[0]);
list_save(&goodvids, "goodvids.txt");
list_save(&badvids, "badvids.txt");
playnext(0);
- }else{say(pm, "Approve what? please specify a video\n");}
+ }else{write(tc_client, "Approve what? please specify a video\n", 37);}
}
else if(!strncmp(msg, "!approve ", 9))
{
- char* vid=&msg[9];
- if(!vid[0]){continue;} // No video specified
- char vidbuf[256];
- if(!strcmp(vid, "next"))
- {
- unsigned int i;
- for(i=0; i<queue.itemcount; ++i)
- {
- if(!list_contains(&goodvids, queue.items[i])){vid=queue.items[i]; break;}
- }
- if(i==queue.itemcount){say(pm, "Nothing more to approve :)\n"); continue;}
- }
- else if(!strcmp(vid, "entire queue"))
- {
- char approved=0;
- unsigned int i;
- for(i=0; i<queue.itemcount; ++i)
- {
- if(list_contains(&goodvids, queue.items[i])){continue;}
- list_add(&goodvids, queue.items[i]);
- approved=1;
- }
- if(approved)
- {
- list_save(&goodvids, "goodvids.txt");
- if(!playing){playnext(0);} // Next in queue just got approved, so play it
- }else{
- say(0, "%s: there is nothing in the queue that isn't already approved, please do not overuse this function\n", nick);
- }
- continue;
- }else{
- getvidinfo(vid, "--get-id", vidbuf, 256);
- vid=vidbuf;
- }
+ char* vid=getyoutube(&msg[9]);
+ if(!vid[0]){vid=playing; if(!vid){continue;}}
list_add(&goodvids, vid);
- list_del(&badvids, vid);
list_save(&goodvids, "goodvids.txt");
- list_save(&badvids, "badvids.txt");
if(!playing && queue.itemcount>0 && !strcmp(vid, queue.items[0])){playnext(0);} // Next in queue just got approved, so play it
}
else if(!strcmp(msg, "!badvid") || !strncmp(msg, "!badvid ", 8))
{
- if(!msg[7] && !playing){say(pm, "Nothing is playing, please use !badvid <URL/ID> instead\n"); continue;}
- char vid[1024];
- if(msg[7])
- {
- getvidinfo(&msg[8], "--get-id", vid, 256);
- }else{strncpy(vid, playing, 1023); vid[1023]=0;}
- if(!vid[0]){say(pm, "Video not found, sorry\n");}
+ char* vid=(msg[7]?&msg[8]:playing);
+ if(!vid){say(pm, "Nothing is playing, please use !badvid <URL/ID> instead\n"); continue;}
list_del(&queue, vid);
list_del(&goodvids, vid);
list_add(&badvids, vid);
list_save(&goodvids, "goodvids.txt");
list_save(&badvids, "badvids.txt");
- if(!strcmp(vid, playing)){say(0, "/mbc youTube\n"); playnext(0);}
+ 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");
- free(playing);
- playing=strdup(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))
@@ -563,14 +438,6 @@ 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);
- }
- }
}
}
}