$ git clone http://tcclient.ion.nu/tc_client.git
commit 23205c603267895c449bf613500a099001d9729c
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.28
diff --git a/ChangeLog b/ChangeLog
index d591221..99ac586 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+0.28:
+Delete streams when they stop, to allow reusing stream IDs.
+Added a /camdown command.
+camviewer: worked around the cam streaming issue.
+camviewer: switched to libavresample instead of libswresample (when available)
+modbot: added a !modstats command to see how often there are moderators present.
+modbot: moved the help-text into an HTML document.
+modbot: added a -v/--verbose option to print/log all incoming messages.
+modbot: pass on error messages from youtube-dl when requesting a video fails.
+cursedchat: added history (up/down arrows)
0.27:
Added some more compatibility hacks for android.
Added support for video broadcasting (adding /camup and /video <length> followed by the encoded data for each frame)
diff --git a/Makefile b/Makefile
index 4480abe..72a37d8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.27
+VERSION=0.28
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
ifneq ($(wildcard config.mk),)
@@ -16,10 +16,13 @@ ifdef AVUTIL_LIBS
ifdef SWSCALE_LIBS
UTILS+=camviewer
CFLAGS+=$(GTK_CFLAGS) $(AVCODEC_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
- ifdef SWRESAMPLE_LIBS
ifdef AO_LIBS
- CFLAGS+=-DHAVE_SOUND $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
- endif
+ ifdef AVRESAMPLE_LIBS
+ CFLAGS+=-DHAVE_SOUND=avresample $(AVRESAMPLE_CFLAGS) $(AO_CFLAGS)
+ endif
+ ifdef SWRESAMPLE_LIBS
+ CFLAGS+=-DHAVE_SOUND=swresample $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
+ endif
endif
ifdef LIBV4L2_LIBS
CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
@@ -47,7 +50,7 @@ modbot: $(MODBOT_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
camviewer: $(CAMVIEWER_OBJ)
- $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) -o $@
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) -o $@
cursedchat: $(CURSEDCHAT_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
@@ -56,4 +59,4 @@ clean:
rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) $(CURSEDCHAT_OBJ) tc_client irchack modbot camviewer cursedchat
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 media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h media.h LICENSE README ChangeLog crossbuild.sh utilities/irchack/irchack.c utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h utilities/compat.c utilities/compat.h utilities/list.c utilities/list.h configure
+ 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 media.c amfparser.h rtmp.h numlist.h amfwriter.h idlist.h colors.h endian.h media.h LICENSE README ChangeLog crossbuild.sh utilities/irchack/irchack.c utilities/modbot/modbot.c utilities/modbot/queue.c utilities/modbot/queue.h utilities/modbot/commands.html utilities/camviewer/camviewer.c utilities/cursedchat/cursedchat.c utilities/cursedchat/buffer.c utilities/cursedchat/buffer.h utilities/compat.c utilities/compat.h utilities/list.c utilities/list.h configure
diff --git a/README b/README
index 438e4b5..b4aa87e 100644
--- a/README
+++ b/README
@@ -16,6 +16,7 @@ Commands supported by tc_client:
/forgive <nick/ID> = unban someone
/camup = open an audio/video stream for broadcasting your video
/video <length> = send a <length> bytes long encoded frame, send the frame data after this line
+/camdown = close the audio/video stream
/help = list these commands at runtime
Current commands sent by the TC servers that tc_client doesn't know how to handle:
diff --git a/client.c b/client.c
index 48af852..b259d22 100644
--- a/client.c
+++ b/client.c
@@ -434,6 +434,7 @@ int main(int argc, char** argv)
"/mute = temporarily mutes all non-moderator broadcasts\n"
"/push2talk = puts all non-operators in push2talk mode\n"
"/camup = open an audio/video stream for broadcasting your video\n"
+ "/camdown = close the broadcasting stream\n"
"/video <length> = send a <length> bytes long encoded frame, send the frame data after this line\n");
fflush(stdout);
}
@@ -590,6 +591,11 @@ int main(int argc, char** argv)
streamout_start(idlist_get(nickname), sock);
continue;
}
+ else if(!strcmp(buf, "/camdown"))
+ {
+ stream_stopvideo(sock);
+ continue;
+ }
else if(!strncmp(buf, "/video ", 7)) // Send video data
{
size_t len=strtoul(&buf[7],0,10);
@@ -875,7 +881,7 @@ int main(int argc, char** argv)
}
else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "onStatus"))
{
- stream_handlestatus(amfin);
+ stream_handlestatus(amfin, sock);
}
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
diff --git a/configure b/configure
index 5bff5c5..166d010 100755
--- a/configure
+++ b/configure
@@ -89,14 +89,23 @@ fi
# TODO: Figure out how to mix sound sources so that this doesn't sound horrible with more than one person on mic, having it disabled by default until then
if [ "x${ENABLE_MIC}" != "x" ]; then
-printf 'Checking for libswresample... '
-libs="`pkg-config --libs libswresample 2> /dev/null`"
+printf 'Checking for libavresample... '
+libs="`pkg-config --libs libavresample 2> /dev/null`"
if [ "x$libs" != "x" ]; then
- echo "SWRESAMPLE_LIBS=${libs}" >> config.mk
- echo "SWRESAMPLE_CFLAGS=`pkg-config --cflags libswresample`" >> config.mk
+ echo "AVRESAMPLE_LIBS=${libs}" >> config.mk
+ echo "AVRESAMPLE_CFLAGS=`pkg-config --cflags libavresample`" >> config.mk
echo yes
else
echo no
+ printf 'Checking for libswresample... '
+ libs="`pkg-config --libs libswresample 2> /dev/null`"
+ if [ "x$libs" != "x" ]; then
+ echo "SWRESAMPLE_LIBS=${libs}" >> config.mk
+ echo "SWRESAMPLE_CFLAGS=`pkg-config --cflags libswresample`" >> config.mk
+ echo yes
+ else
+ echo no
+ fi
fi
printf 'Checking for libao... '
diff --git a/media.c b/media.c
index 649c2f7..7e2e166 100644
--- a/media.c
+++ b/media.c
@@ -70,6 +70,7 @@ void streamout_start(unsigned int id, int sock) // called upon privmsg "/camup"
amfnull(&amf);
amfsend(&amf, sock);
printf("Starting outgoing media stream\n");
+ fflush(stdout);
}
void stream_play(struct amf* amf, int sock) // called upon _result
@@ -115,7 +116,7 @@ void stream_handledata(struct rtmp* rtmp)
printf("Received media data to unknown stream ID %u\n", rtmp->msgid);
}
-void stream_handlestatus(struct amf* amf)
+void stream_handlestatus(struct amf* amf, int sock)
{
if(amf->itemcount<3 || amf->items[2].type!=AMF_OBJECT){return;}
struct amfobject* obj=&amf->items[2].object;
@@ -132,7 +133,17 @@ void stream_handlestatus(struct amf* amf)
if(streams[i].userid==id)
{
printf("VideoEnd: %u\n", streams[i].userid);
- // Note: not removing it from the list because tinychat doesn't seem to handle reusing stream IDs
+ // Delete the stream
+ struct rtmp amf;
+ amfinit(&amf, 3);
+ amfstring(&amf, "deleteStream");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, streams[i].streamid);
+ amfsend(&amf, sock);
+ // Remove from list of streams
+ --streamcount;
+ memmove(&streams[i], &streams[i+1], sizeof(struct stream)*(streamcount-i));
return;
}
}
@@ -157,3 +168,33 @@ void stream_sendvideo(int sock, void* buf, size_t len)
}
}
}
+
+void stream_stopvideo(int sock)
+{
+ unsigned int i;
+ for(i=0; i<streamcount; ++i)
+ {
+ if(streams[i].outgoing)
+ {
+ struct rtmp amf;
+ // Close the stream
+ amfinit(&amf, 8);
+ amfstring(&amf, "closeStream");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amf.msgid=le32(streams[i].streamid);
+ amfsend(&amf, sock);
+ // Delete the stream
+ amfinit(&amf, 3);
+ amfstring(&amf, "deleteStream");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfnum(&amf, streams[i].streamid);
+ amfsend(&amf, sock);
+ // Remove from list of streams
+ --streamcount;
+ memmove(&streams[i], &streams[i+1], sizeof(struct stream)*(streamcount-i));
+ return;
+ }
+ }
+}
diff --git a/media.h b/media.h
index 71893cf..25195ab 100644
--- a/media.h
+++ b/media.h
@@ -30,5 +30,6 @@ extern void stream_start(const char* nick, int sock); // called upon privmsg "/o
extern void streamout_start(unsigned int id, int sock); // called upon privmsg "/camup"
extern void stream_play(struct amf* amf, int sock); // called upon _result
extern void stream_handledata(struct rtmp* rtmp);
-extern void stream_handlestatus(struct amf* amf);
+extern void stream_handlestatus(struct amf* amf, int sock);
extern void stream_sendvideo(int sock, void* buf, size_t len);
+extern void stream_stopvideo(int sock);
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index df8ad53..a11142b 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -24,11 +24,15 @@
#include <libavutil/imgutils.h>
#ifdef HAVE_SOUND
// TODO: use libavresample instead if available
- #include <libswresample/swresample.h>
+ #if HAVE_SOUND==avresample
+ #include <libavutil/opt.h>
+ #include <libavresample/avresample.h>
+ #else
+ #include <libswresample/swresample.h>
+ #endif
#include <ao/ao.h>
#endif
#include <gtk/gtk.h>
-#undef HAVE_V4L2 // Not working yet, something is wrong with the frames making the encoding break (for keyframes in particular and I don't know why)
#ifdef HAVE_V4L2
#include <libv4l2.h>
#include <linux/videodev2.h>
@@ -68,11 +72,15 @@ struct viddata
unsigned int camcount;
GtkWidget* box;
AVCodec* vdecoder;
-// AVCodec* vencoder;
+ AVCodec* vencoder;
AVCodec* adecoder;
#ifdef HAVE_SOUND
int audiopipe;
- SwrContext* swrctx;
+ #if HAVE_SOUND==avresample
+ AVAudioResampleContext* resamplectx;
+ #else
+ SwrContext* swrctx;
+ #endif
#endif
};
@@ -175,7 +183,6 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
gtk_widget_show_all(cam->box);
return 1;
}
-/*
if(!strcmp(buf, "Starting outgoing media stream"))
{
++data->camcount;
@@ -195,7 +202,6 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
gtk_widget_show_all(cam->box);
return 1;
}
-*/
if(!strncmp(buf, "VideoEnd: ", 10))
{
for(i=0; i<data->camcount; ++i)
@@ -248,7 +254,11 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
int gotframe;
avcodec_decode_audio4(cam->actx, cam->frame, &gotframe, &pkt);
if(!gotframe){return 1;}
- int outlen=swr_convert(data->swrctx, cam->frame->data, cam->frame->nb_samples, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
+ #if HAVE_SOUND==avresample
+ int outlen=avresample_convert(data->resamplectx, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples, cam->frame->data, cam->frame->linesize[0], cam->frame->nb_samples);
+ #else
+ int outlen=swr_convert(data->resamplectx, cam->frame->data, cam->frame->nb_samples, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
+ #endif
camera_playsnd(data, cam, (short*)cam->frame->data[0], outlen);
#endif
return 1;
@@ -328,12 +338,23 @@ void audiothread(int fd)
#endif
#ifdef HAVE_V4L2
-void camup(GtkWidget* button, struct viddata* data)
+pid_t camproc=0;
+void togglecam(GtkButton* button, struct viddata* data)
{
+ if(camproc)
+ {
+ kill(camproc, SIGINT);
+ camproc=0;
+ gtk_button_set_label(button, "Broadcast cam");
+ dprintf(tc_client_in[1], "/camdown\n");
+ dprintf(tc_client[1], "VideoEnd: out\n"); // Close our local display
+ return;
+ }
dprintf(tc_client_in[1], "/camup\n");
- gtk_widget_destroy(button); // Only once
+ gtk_button_set_label(button, "Stop broadcasting");
// printf("Camming up!\n");
- if(!fork())
+ camproc=fork();
+ if(!camproc)
{
prctl(PR_SET_PDEATHSIG, SIGHUP);
unsigned int delay=500000;
@@ -387,9 +408,7 @@ void camup(GtkWidget* button, struct viddata* data)
packet.data=0;
packet.size=0;
avcodec_encode_video2(ctx, &packet, dstframe, &gotpacket);
- unsigned char frameinfo=0x20;
-// if(packet.flags&AV_PKT_FLAG_KEY){printf("Sending keyframe!\n");}
- if(packet.flags&AV_PKT_FLAG_KEY){frameinfo|=0x01;}else{frameinfo|=0x02;}
+ unsigned char frameinfo=0x22; // Note: differentiating between keyframes and non-keyframes seems to break stuff, so let's just go with all being interframes (1=keyframe, 2=interframe, 3=disposable interframe)
dprintf(tc_client_in[1], "/video %i\n", packet.size+1);
write(tc_client_in[1], &frameinfo, 1);
write(tc_client_in[1], packet.data, packet.size);
@@ -414,8 +433,20 @@ int main(int argc, char** argv)
data.adecoder=avcodec_find_decoder(AV_CODEC_ID_NELLYMOSER);
#ifdef HAVE_SOUND
- data.swrctx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0); // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
+ #if HAVE_SOUND==avresample
+ data.resamplectx=avresample_alloc_context();
+ av_opt_set_int(data.resamplectx, "in_channel_layout", AV_CH_FRONT_CENTER, 0);
+ av_opt_set_int(data.resamplectx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
+ // TODO: any way to get the sample rate from the frame/decoder? cam->frame->sample_rate seems to be 0
+ av_opt_set_int(data.resamplectx, "in_sample_rate", 11025, 0);
+ av_opt_set_int(data.resamplectx, "out_channel_layout", AV_CH_FRONT_CENTER, 0);
+ av_opt_set_int(data.resamplectx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
+ av_opt_set_int(data.resamplectx, "out_sample_rate", 22050, 0);
+ avresample_open(data.resamplectx);
+ #else
+ data.resamplectx=swr_alloc_set_opts(0, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_S16, 22050, AV_CH_FRONT_CENTER, AV_SAMPLE_FMT_FLT, 11025, 0, 0);
swr_init(data.swrctx);
+ #endif
int audiopipe[2];
pipe(audiopipe);
data.audiopipe=audiopipe[1];
@@ -437,7 +468,7 @@ int main(int argc, char** argv)
#ifdef HAVE_V4L2
data.vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
GtkWidget* cambutton=gtk_button_new_with_label("Broadcast cam");
- g_signal_connect(cambutton, "clicked", G_CALLBACK(camup), &data);
+ g_signal_connect(cambutton, "clicked", G_CALLBACK(togglecam), &data);
gtk_box_pack_start(GTK_BOX(data.box), cambutton, 0, 0, 0);
#endif
gtk_widget_show_all(w);
@@ -470,7 +501,11 @@ int main(int argc, char** argv)
avcodec_free_context(&data.cams[i].vctx);
#ifdef HAVE_SOUND
avcodec_free_context(&data.cams[i].actx);
+ #if HAVE_SOUND==avresample
+ avresample_free(&data.resamplectx);
+ #else
swr_free(&data.swrctx);
+ #endif
#endif
free(data.cams[i].id);
free(data.cams[i].nick);
diff --git a/utilities/cursedchat/cursedchat.c b/utilities/cursedchat/cursedchat.c
index 8a30f75..c61bc98 100644
--- a/utilities/cursedchat/cursedchat.c
+++ b/utilities/cursedchat/cursedchat.c
@@ -23,6 +23,7 @@
#include <locale.h>
#include <curses.h>
#include <readline/readline.h>
+#include <readline/history.h>
#include "../compat.h"
#include "../list.h"
#include "buffer.h"
@@ -115,6 +116,7 @@ void drawtopic(void)
void gotline(char* line)
{
if(!line){close(to_app); return;} // TODO: handle EOF on stdin better?
+ add_history(line);
if(!strcmp(line, "/pm"))
{
currentbuf=0;
@@ -218,8 +220,8 @@ int escinput(int a, int byte)
char buf[4];
read(0, buf, 2);
buf[2]=0;
- if(!strcmp(buf, "[A")||!strcmp(buf, "OA")){return 0;} // TODO: history?
- if(!strcmp(buf, "[B")||!strcmp(buf, "OB")){return 0;} // TODO: history?
+ if(!strcmp(buf, "[A")||!strcmp(buf, "OA")){rl_get_previous_history(1,27);return 0;}
+ if(!strcmp(buf, "[B")||!strcmp(buf, "OB")){rl_get_next_history(1,27);return 0;}
if(!strcmp(buf, "[C")||!strcmp(buf, "OC")){rl_forward(1,27);return 0;}
if(!strcmp(buf, "[D")||!strcmp(buf, "OD")){rl_backward(1,27);return 0;}
if(!strcmp(buf, "[H")||!strcmp(buf, "OH")){rl_beg_of_line(1,27);return 0;}
diff --git a/utilities/modbot/commands.html b/utilities/modbot/commands.html
new file mode 100644
index 0000000..c9477b2
--- /dev/null
+++ b/utilities/modbot/commands.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+ <title>modbot commands</title>
+ <style>
+ <!--
+ th{text-align:left;}
+ -->
+ </style>
+</head>
+<body>
+ modbot is a utility to queue videos to be played, with a list of approved videos that will automatically play when requested. Below is a list of supported commands.<br /><br />
+ <table cellspacing="1" border="0">
+ <tr><th>Command</th><th>Description</th></tr>
+ <tr><td>!request <link/searchterm></td><td>request a video to be played</td></tr>
+ <tr><td>!queue</td><td>get the number of songs in queue and which (if any) need to be approved</td></tr>
+ <tr><td>!wrongrequest</td><td>undo the last request you made</td></tr>
+ <tr><td>!requestedby</td><td>see who requested the current video</td></tr>
+ <tr><td>!modstats</td><td>get a percentage of how often there are mods in the channel (aside from modbot)</td></tr>
+ <tr><th colspan="2">Mod commands:</th></tr>
+ <tr><td>!playnext</td><td>play the next video in queue without approving it (to see if it's ok)</td></tr>
+ <tr><td>!approve</td><td>mark the currently playing video as good, or if none is playing the next in queue</td></tr>
+ <tr><td>!approve <link/searchterm></td><td>mark the specified video as okay</td></tr>
+ <tr><td>!approve next</td><td>mark the next not yet approved video as okay</td></tr>
+ <tr><td>!approve entire queue</td><td>approve all videos in queue (for playlists)</td></tr>
+ <tr><td>!badvid</td><td>stop playing the current video and mark it as bad</td></tr>
+ <tr><td>!badvid <link/searchterm></td><td>mark the specified video as bad, preventing it from ever being queued again</td></tr>
+ </table><br />
+ You can also just play videos manually/the good old way and they will be marked as good.
+</body>
+</html>
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index 3e84a77..86ed03b 100644
--- a/utilities/modbot/modbot.c
+++ b/utilities/modbot/modbot.c
@@ -38,6 +38,20 @@ char* requester=0;
time_t started=0;
int tc_client;
+time_t time_with_mods=0;
+time_t time_modcount;
+char havemods=0;
+void timemods(void)
+{
+ time_t now=time(0);
+ if(havemods)
+ {
+ time_with_mods+=now-time_modcount;
+ }
+ time_modcount=now;
+ havemods=(mods.itemcount>1); // Not counting modbot as a mod
+}
+
void say(const char* pm, const char* fmt, ...)
{
va_list va;
@@ -57,32 +71,51 @@ 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 getvidinfo(const char* vid, const char* type, char* buf, char* errbuf, unsigned int len)
{
int out[2];
+ int err[2];
pipe(out);
+ pipe(err);
if(!fork())
{
close(out[0]);
+ close(err[0]);
dup2(out[1], 1);
+ dup2(err[1], 2);
execlp("youtube-dl", "youtube-dl", "--default-search", "auto", type, "--", vid, (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(err[1]);
+ size_t r;
+ // Read output
+ r=read(out[0], buf, len-1);
+ if(r<0){r=0;}
+ while(r>0 && (buf[r-1]=='\r' || buf[r-1]=='\n')){--r;} // Strip newlines
+ buf[r]=0;
close(out[0]);
+ // Read any error messages
+ if(errbuf)
+ {
+ r=read(err[0], errbuf, len-1);
+ if(r<0){r=0;}
+ while(r>0 && (errbuf[r-1]=='\r' || errbuf[r-1]=='\n')){--r;} // Strip newlines
+ errbuf[r]=0;
+ char* newline; // No need for newlines in error messages
+ while((newline=strchr(errbuf, '\n'))){newline[0]=' ';}
+ while((newline=strchr(errbuf, '\r'))){newline[0]=' ';}
+ }
+ close(err[0]);
}
unsigned int getduration(const char* vid)
{
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);
+ getvidinfo(vid, "--get-duration", &timebuf[1], 0, 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
// youtube-dl prints it out in hh:mm:ss format, convert it to plain seconds
unsigned int len;
@@ -153,6 +186,7 @@ int main(int argc, char** argv)
// Handle arguments (-d, -l, -h additions, the rest are handled by tc_client)
char daemon=0;
char* logfile=0;
+ char verbose=0;
unsigned int i;
for(i=1; i<argc; ++i)
{
@@ -174,11 +208,21 @@ int main(int argc, char** argv)
argv[argc]=0;
--i;
}
+ else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
+ {
+ verbose=1;
+ // Remove non-tc_client argument
+ --argc;
+ memmove(&argv[i], &argv[i+1], sizeof(char*)*(argc-i));
+ argv[argc]=0;
+ --i;
+ }
else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
{
printf("Additional options for modbot:\n"
"-d/--daemon = daemonize after startup\n"
"-l/--log <file> = log output into <file>\n"
+ "-v/--verbose = print/log all incoming messages\n"
"\n");
execv("./tc_client", argv);
return 1;
@@ -205,6 +249,8 @@ int main(int argc, char** argv)
list_load(&badvids, "badvids.txt");
char buf[1024];
int len=0;
+ time_t sessionstart=time(0);
+ time_modcount=sessionstart;
while(1)
{
if(read(out[0], &buf[len], 1)<1){break;}
@@ -237,7 +283,6 @@ int main(int argc, char** argv)
memmove(esc, &esc[len+1], strlen(&esc[len]));
}
len=0;
- // printf("Got line '%s'\n", buf);
// Note: daemonizing and setting up logging here to avoid interfering with account password entry
if(daemon)
{
@@ -253,6 +298,7 @@ int main(int argc, char** argv)
close(f);
logfile=0;
}
+ if(verbose){printf("Got line '%s'\n", buf); fflush(stdout);}
char* space=strchr(buf, ' ');
if(!space){continue;}
if(!strcmp(space, " is a moderator."))
@@ -260,12 +306,14 @@ int main(int argc, char** argv)
// If there are not-yet-approved videos in the queue when a mod joins, ask them to review them
space[0]=0;
list_add(&mods, buf);
+ timemods();
continue;
}
if(!strcmp(space, " is no longer a moderator."))
{
space[0]=0;
list_del(&mods, buf);
+ timemods();
continue;
}
space[0]=0;
@@ -291,19 +339,24 @@ int main(int argc, char** argv)
{
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 viderr[1024];
+ getvidinfo(&msg[9], "--get-id", vid, viderr, 1024);
+ if(!vid[0]) // Nothing found
+ {
+ say(pm, "No video found, sorry (%s)\n", viderr);
+ continue;
+ }
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);
+ getvidinfo(vid, "--get-title", &title[24], 0, 256-24);
plist[0]='\n';
}else{
plist=0;
- getvidinfo(vid, "--get-title", title, 256);
+ getvidinfo(vid, "--get-title", title, 0, 256);
}
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
@@ -362,7 +415,7 @@ int main(int argc, char** argv)
else if(!strncmp(msg, "!wrongrequest ", 14))
{
char vid[1024];
- getvidinfo(&msg[14], "--get-id", vid, 1024);
+ getvidinfo(&msg[14], "--get-id", vid, 0, 1024);
unsigned int i;
for(i=0; i<queue.itemcount; ++i)
{
@@ -421,33 +474,30 @@ int main(int argc, char** argv)
}
else if(!strcmp(msg, "!help"))
{
- say(nick, "The following commands can be used:\n");
- usleep(500000);
- say(nick, "!request <link> = request a video to be played\n");
- usleep(500000);
- say(nick, "!queue = get the number of songs in queue and which (if any) need to be approved\n");
- usleep(500000);
- say(nick, "Mod commands:\n"); // TODO: don't bother filling non-mods' chats with these?
- usleep(500000);
- say(nick, "!playnext = play the next video in queue without approving it (to see if it's ok)\n");
- usleep(500000);
- say(nick, "!approve = mark the currently playing video as good, or if none is playing the next in queue\n");
- usleep(500000);
- say(nick, "!approve <link> = mark the specified video as okay\n");
- usleep(500000);
- say(nick, "!approve next = mark the next not yet approved video as okay\n");
- usleep(500000);
- say(nick, "!approve entire queue = approve all videos in queue (for playlists)\n");
- usleep(500000);
- say(nick, "!badvid = stop playing the current video and mark it as bad\n");
- usleep(500000);
- say(nick, "!badvid <link> = mark the specified video as bad, preventing it from ever being queued again\n");
- usleep(500000);
- say(nick, "!wrongrequest = undo the last request you made\n");
- usleep(500000);
- say(nick, "!requestedby = see who requested the current video\n");
- usleep(500000);
- say(nick, "You can also just play videos manually/the good old way and they will be marked as good.\n");
+ say(pm, "http://tc_client.ion.nu/misc/modbotcommands.html\n");
+ }
+ else if(!strcmp(msg, "!modstats"))
+ {
+ unsigned int session=time(0)-sessionstart;
+ timemods();
+ unsigned int hasmods=time_with_mods*100/session;
+ const char* timeformat="seconds";
+ if(session>=120)
+ {
+ session/=60;
+ timeformat="minutes";
+ if(session>=120)
+ {
+ session/=60;
+ timeformat="hours";
+ if(session>=48)
+ {
+ session/=24;
+ timeformat="days";
+ }
+ }
+ }
+ say(pm, "The channel has had mods %u%% of the time for the past %u %s\n", hasmods, session, timeformat);
}
else if(list_contains(&mods, nick)) // Mods-only commands
{
@@ -514,7 +564,7 @@ int main(int argc, char** argv)
}
continue;
}else{
- getvidinfo(vid, "--get-id", vidbuf, 256);
+ getvidinfo(vid, "--get-id", vidbuf, 0, 256);
vid=vidbuf;
}
list_add(&goodvids, vid);
@@ -530,7 +580,7 @@ int main(int argc, char** argv)
char vid[1024];
if(space && space[1])
{
- getvidinfo(&space[1], "--get-id", vid, 256);
+ getvidinfo(&space[1], "--get-id", vid, 0, 256);
}else{strncpy(vid, playing, 1023); vid[1023]=0;}
if(!vid[0]){say(pm, "Video not found, sorry\n"); continue;}
queue_del(&queue, vid);
@@ -613,6 +663,12 @@ int main(int argc, char** argv)
say(0, "/priv %s /mbs youTube %s %u\n", nick, playing, (time(0)-started)*1000);
}
}
+ else if(!strcmp(space, " left the channel"))
+ {
+ space[0]=0;
+ list_del(&mods, nick); // Absent people can't be mods
+ timemods();
+ }
}
}
}