$ git clone http://tcclient.ion.nu/tc_client.git
commit 735097b0a0c2bcfbfdde1fad738c9f2ca99a6699
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.23
diff --git a/ChangeLog b/ChangeLog
index 424a400..1c9f9f1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+0.23:
+Added the moderator commands /mute and /push2talk (contributed by Jade)
+Set tc_client as a dependency of utils.
+Implemented RTMP message type 1 (set chunk size)
+Writing audio data the same way video data is written.
+camviewer: fixed a bug where joining a channel where no one is on cam resulted in trying to view the cam for "", leading to an empty GtkImage in the row of cams.
+camviewer: added labels under each image with people's nicknames.
+Added a basic ncurses utility called cursedchat.
+modbot: for manual /mbc, remove the video from goodvids.
+modbot: fixed a bug that caused a segfault when using !badvid if nothing was playing.
+modbot: added a response for !wrongrequest without an argument when no request is found.
+modbot: renamed !whorequested to !requestedby for the sake of immature minds.
0.22:
Cleaned up media.c/h which I forgot to clean up before releasing 0.21.
Detect when someone cams down.
@@ -53,7 +65,7 @@ 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)
+modbot: handle manual /mbs and /mbc commands (remove from queue, mark as good for /mbs)
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:
diff --git a/Makefile b/Makefile
index f943ec4..e780924 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.22
+VERSION=0.23
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
ifneq ($(wildcard config.mk),)
@@ -8,6 +8,7 @@ OBJ=client.o amfparser.o rtmp.o numlist.o amfwriter.o idlist.o colors.o endian.o
IRCHACK_OBJ=utilities/irchack/irchack.o
MODBOT_OBJ=utilities/modbot/modbot.o utilities/modbot/list.o utilities/modbot/queue.o
CAMVIEWER_OBJ=utilities/camviewer/camviewer.o
+CURSEDCHAT_OBJ=utilities/cursedchat/cursedchat.o
UTILS=irchack modbot
ifdef GTK_LIBS
ifdef AVCODEC_LIBS
@@ -19,11 +20,17 @@ endif
endif
endif
endif
+ifdef CURSES_LIBS
+ifdef READLINE_LIBS
+ UTILS+=cursedchat
+ CFLAGS+=$(CURSES_CFLAGS)
+endif
+endif
tc_client: $(OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
-utils: $(UTILS)
+utils: $(UTILS) tc_client
irchack: $(IRCHACK_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
@@ -34,8 +41,11 @@ modbot: $(MODBOT_OBJ)
camviewer: $(CAMVIEWER_OBJ)
$(CC) $(LDFLAGS) $^ $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) -o $@
+cursedchat: $(CURSEDCHAT_OBJ)
+ $(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
+
clean:
- rm -f $(OBJ) $(IRCHACK_OBJ) $(MODBOT_OBJ) $(CAMVIEWER_OBJ) tc_client irchack modbot camviewer
+ 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/list.c utilities/modbot/list.h utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c 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/list.c utilities/modbot/list.h utilities/modbot/queue.c utilities/modbot/queue.h utilities/camviewer/camviewer.c utilities/cursedchat/cursedchat.c configure
diff --git a/amfparser.c b/amfparser.c
index 35e9a76..c9829ef 100644
--- a/amfparser.c
+++ b/amfparser.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
diff --git a/amfparser.h b/amfparser.h
index 96d9a1c..7244445 100644
--- a/amfparser.h
+++ b/amfparser.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
diff --git a/client.c b/client.c
index e7f8824..9cd746d 100644
--- a/client.c
+++ b/client.c
@@ -226,6 +226,7 @@ int main(int argc, char** argv)
if(!strcmp(argv[i], "-h")||!strcmp(argv[i], "--help")){usage(argv[0]); return 0;}
else if(!strcmp(argv[i], "-u")||!strcmp(argv[i], "--user"))
{
+ if(i+1==argc){continue;}
++i;
account_user=argv[i];
unsigned int j;
@@ -233,6 +234,7 @@ int main(int argc, char** argv)
}
else if(!strcmp(argv[i], "-p")||!strcmp(argv[i], "--pass"))
{
+ if(i+1==argc){continue;}
++i;
account_pass=strdup(argv[i]);
}
@@ -401,7 +403,9 @@ int main(int argc, char** argv)
"/ban <nick> = ban someone\n"
"/banlist = list who is banned\n"
"/forgive <nick/ID> = unban someone\n"
- "/names = list everyone that is online\n");
+ "/names = list everyone that is online\n"
+ "/mute = temporarily mutes all non-moderator broadcasts\n"
+ "/push2talk = puts all non-operators in push2talk mode\n");
fflush(stdout);
}
else if(!strncmp(buf, "/color", 6) && (!buf[6]||buf[6]==' '))
@@ -522,6 +526,26 @@ int main(int argc, char** argv)
fflush(stdout);
continue;
}
+ else if(!strcmp(buf, "/mute"))
+ {
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, "mute");
+ amfsend(&amf, sock);
+ continue;
+ }
+ else if(!strcmp(buf, "/push2talk"))
+ {
+ amfinit(&amf, 3);
+ amfstring(&amf, "owner_run");
+ amfnum(&amf, 0);
+ amfnull(&amf);
+ amfstring(&amf, "push2talk");
+ amfsend(&amf, sock);
+ continue;
+ }
}
char* msg=tonumlist(buf, len);
amfinit(&amf, 3);
@@ -546,7 +570,7 @@ int main(int argc, char** argv)
char rtmpres=rtmp_get(sock, &rtmp);
if(!rtmpres){printf("Server disconnected\n"); break;}
if(rtmpres==2){continue;} // Not disconnected, but we didn't get a complete chunk yet either
- if(rtmp.type==RTMP_VIDEO){stream_handledata(&rtmp); continue;}
+ if(rtmp.type==RTMP_VIDEO || rtmp.type==RTMP_AUDIO){stream_handledata(&rtmp); continue;}
if(rtmp.type!=RTMP_AMF0){printf("Got RTMP type 0x%x\n", rtmp.type); continue;}
struct amf* amfin=amf_parse(rtmp.buf, rtmp.length);
if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING)
@@ -690,6 +714,16 @@ int main(int argc, char** argv)
printf("%s %s\n", timestamp(), notice);
fflush(stdout);
}
+ else if(!strncmp("mute", amfin->items[2].string.string, 4))
+ {
+ printf("%s Non-moderators have been temporarily muted.\n", timestamp());
+ fflush(stdout);
+ }
+ else if(!strncmp("push2talk", amfin->items[2].string.string, 9))
+ {
+ printf("%s Push to talk request has been sent to non-moderators.\n", timestamp());
+ fflush(stdout);
+ }
}
// 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)
diff --git a/configure b/configure
index 955fecc..4d62ba9 100755
--- a/configure
+++ b/configure
@@ -87,4 +87,26 @@ else
echo no
fi
+printf 'Checking for ncurses... '
+libs="`ncursesw5-config --libs 2> /dev/null || ncurses5-config --libs 2> /dev/null`"
+if [ "x$libs" != "x" ]; then
+ echo "CURSES_LIBS=${libs}" >> config.mk
+ echo "CURSES_CFLAGS=`ncursesw5-config --cflags 2> /dev/null || ncurses5-config --cflags 2> /dev/null`" >> config.mk
+ echo yes
+else
+ echo no
+fi
+
+printf 'Checking for readline... '
+echo '#include <stdio.h>' > readlinetest.c
+echo '#include <readline/readline.h>' >> readlinetest.c
+echo 'int main(){rl_initialize();return 0;}' >> readlinetest.c
+if "$CC" readlinetest.c -lreadline ${libs} -o readlinetest > /dev/null 2> /dev/null; then
+ echo "READLINE_LIBS=-lreadline" >> config.mk
+ echo yes
+else
+ echo no
+fi
+rm -f readlinetest.c readlinetest
+
echo Done
diff --git a/media.c b/media.c
index 18157be..a4e7bf3 100644
--- a/media.c
+++ b/media.c
@@ -82,7 +82,12 @@ void stream_handledata(struct rtmp* rtmp)
{
if(streams[i].streamid!=rtmp->msgid){continue;}
// fprintf(stderr, "Chunk: chunkid: %u, streamid: %u, userid: %u\n", rtmp->chunkid, rtmp->msgid, streams[i].userid);
- printf("Video: %u %u\n", streams[i].userid, rtmp->length);
+ if(rtmp->type==RTMP_VIDEO)
+ {
+ printf("Video: %u %u\n", streams[i].userid, rtmp->length);
+ }else if(rtmp->type==RTMP_AUDIO){
+ printf("Audio: %u %u\n", streams[i].userid, rtmp->length);
+ }
fwrite(rtmp->buf, rtmp->length, 1, stdout);
fflush(stdout);
return;
@@ -107,8 +112,7 @@ void stream_handlestatus(struct amf* amf)
if(streams[i].userid==id)
{
printf("VideoEnd: %u\n", streams[i].userid);
-// TODO: remove stream from array
-// TODO: destroyStream or something?
+ // Note: not removing it from the list because tinychat doesn't seem to handle reusing stream IDs
return;
}
}
diff --git a/rtmp.c b/rtmp.c
index ab2c29f..443a484 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -33,6 +33,7 @@ struct chunk
};
struct chunk* chunks=0;
unsigned int chunkcount=0;
+unsigned int chunksize_in=128;
struct chunk* chunk_get(unsigned int id)
{
@@ -99,7 +100,7 @@ char rtmp_get(int sock, struct rtmp* rtmp)
chunk->buf=malloc(chunk->length);
chunk->pos=0;
}
- unsigned int rsize=((chunk->length-chunk->pos>127)?128:(chunk->length-chunk->pos));
+ unsigned int rsize=((chunk->length-chunk->pos>=chunksize_in)?chunksize_in:(chunk->length-chunk->pos));
while(rsize>0)
{
size_t r=read(sock, chunk->buf+chunk->pos, rsize);
@@ -110,6 +111,11 @@ char rtmp_get(int sock, struct rtmp* rtmp)
}
if(chunk->pos==chunk->length)
{
+ if(chunk->type==RTMP_SET_PACKET_SIZE)
+ {
+ memcpy(&chunksize_in, chunk->buf, sizeof(unsigned int));
+// printf("Server set chunk size to %u (packet size: %u)\n", chunksize_in, chunk->length);
+ }
// printf("Got chunk: chunkid=%u, type=%u, length=%u, streamid=%u\n", chunk->id, chunk->type, chunk->length, chunk->streamid);
rtmp->type=chunk->type;
rtmp->chunkid=chunk->id;
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index dca0f35..2b25607 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -21,7 +21,17 @@
#include <gtk/gtk.h>
#if GTK_MAJOR_VERSION==2
- #define gtk_box_new(orientation, spacing) gtk_hbox_new(1, spacing)
+ #define GTK_ORIENTATION_HORIZONTAL 0
+ #define GTK_ORIENTATION_VERTICAL 1
+ GtkWidget* gtk_box_new(int vertical, int spacing)
+ {
+ if(vertical)
+ {
+ return gtk_vbox_new(1, spacing);
+ }else{
+ return gtk_hbox_new(1, spacing);
+ }
+ }
#endif
struct camera
@@ -32,6 +42,7 @@ struct camera
AVCodecContext* ctx;
char* id;
char* nick;
+ GtkWidget* box; // holds label and cam
};
struct viddata
@@ -64,6 +75,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
char* user=&next[2];
next=strstr(user, ", ");
+ if(!user[0]){continue;}
if(next){next[0]=0;}
dprintf(tc_client_in[1], "/opencam %s\n", user);
}
@@ -97,8 +109,11 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
cam->ctx=avcodec_alloc_context3(data->decoder);
avcodec_open2(cam->ctx, data->decoder, 0);
cam->cam=gtk_image_new();
- gtk_box_pack_start(GTK_BOX(data->box), cam->cam, 0, 0, 0);
- gtk_widget_show(cam->cam);
+ cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(cam->box), gtk_label_new(cam->nick), 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
+ gtk_widget_show_all(cam->box);
return 1;
}
if(!strncmp(buf, "VideoEnd: ", 10))
@@ -107,7 +122,7 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
if(!strcmp(data->cams[i].id, &buf[10]))
{
- gtk_widget_destroy(data->cams[i].cam);
+ gtk_widget_destroy(data->cams[i].box);
av_frame_free(&data->cams[i].frame);
avcodec_free_context(&data->cams[i].ctx);
free(data->cams[i].id);
@@ -119,6 +134,21 @@ gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
}
return 1;
}
+ if(!strncmp(buf, "Audio: ", 7))
+ {
+ char* sizestr=strchr(&buf[7], ' ');
+ if(!sizestr){return 1;}
+ unsigned int size=strtoul(&sizestr[1], 0, 0);
+ unsigned char frameinfo;
+ read(tc_client[0], &frameinfo, 1);
+ unsigned char buf[size];
+ unsigned int pos=0;
+ while(pos<size)
+ {
+ pos+=read(tc_client[0], &buf[pos], size-pos);
+ }
+// TODO: decode and send to a sound lib (libao)
+ }
if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); return 1;} // Ignore anything else that isn't video
char* sizestr=strchr(&buf[7], ' ');
if(!sizestr){return 1;}
diff --git a/utilities/cursedchat/cursedchat.c b/utilities/cursedchat/cursedchat.c
new file mode 100644
index 0000000..5da726a
--- /dev/null
+++ b/utilities/cursedchat/cursedchat.c
@@ -0,0 +1,278 @@
+/*
+ cursedchat, a simple curses interface for tc_client
+ 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
+ the Free Software Foundation, version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ 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/>.
+*/
+#include <unistd.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <locale.h>
+#include <curses.h>
+#include <readline/readline.h>
+
+WINDOW* topic;
+WINDOW* chat;
+WINDOW* input;
+int to_app;
+
+// Translate ANSI escape codes to curses commands and write the text to a window
+void waddansi(WINDOW* w, char* str)
+{
+ while(str[0])
+ {
+ char* esc=strstr(str, "\x1b[");
+ if(esc==str)
+ {
+ str=&str[2];
+ while(str[0]!='m')
+ {
+ if(str[0]=='3'&&str[1]!='m') // Color
+ {
+ unsigned int c=strtoul(&str[1], &str, 10);
+ wattron(w, COLOR_PAIR(c+1));
+ }
+ else if(str[0]=='1') // Bold
+ {
+ wattron(w, A_BOLD);
+ str=&str[1];
+ }
+ else if(str[0]=='0') // Reset
+ {
+ wattroff(w, COLOR_PAIR(1));
+ wattroff(w, A_BOLD);
+ str=&str[1];
+ }
+ else{str=&str[1];}
+ }
+ str=&str[1];
+ continue;
+ }
+ if(esc)
+ {
+ waddnstr(w, str, esc-str);
+ str=esc;
+ }else{
+ waddstr(w, str);
+ return;
+ }
+ }
+}
+
+void gotline(char* line)
+{
+ if(!line){close(to_app); return;} // TODO: handle EOF on stdin better?
+// TODO: handle commands (/pm, /help addition)
+
+ write(to_app, line, strlen(line));
+ write(to_app, "\n", 1);
+// TODO: grab user's nick for this
+ wprintw(chat, "\n%s: %s", "You", line);
+ wrefresh(chat);
+}
+
+unsigned int bytestochars(const char* buf, unsigned int buflen, unsigned int bytes)
+{
+ unsigned int pos=0;
+ unsigned int i;
+ for(i=0; i<bytes; ++pos)
+ {
+ i+=mbtowc(0,&buf[i],buflen-i);
+ }
+ return pos;
+}
+
+unsigned int charstobytes(const char* buf, unsigned int buflen, unsigned int chars)
+{
+ unsigned int pos;
+ unsigned int i=0;
+ for(pos=0; pos<chars; ++pos)
+ {
+ i+=mbtowc(0,&buf[i],buflen-i);
+ }
+ return i;
+}
+
+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, "[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;}
+ if(!strcmp(buf, "[F")||!strcmp(buf, "OF")){rl_end_of_line(1,27);return 0;}
+ if(!strcmp(buf, "[3")&&read(0, buf, 1)&&buf[0]=='~'){rl_delete(1,27);return 0;}
+ if(!strcmp(buf, "[5"))
+ {
+ read(0, buf, 1);
+ wprintw(chat, "\nTODO: handle Page up");
+// wscrl(chat, chat->_maxy/2);
+ wrefresh(chat);
+ return 0;
+ }
+ if(!strcmp(buf, "[6"))
+ {
+ read(0, buf, 1);
+ wprintw(chat, "\nTODO: handle Page down");
+// wscrl(chat, -chat->_maxy/2);
+ wrefresh(chat);
+ return 0;
+ }
+// wprintw(chat, "\nbuf: %s", buf);
+ return 0;
+}
+
+void drawinput()
+{
+ werase(input);
+ unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
+
+ waddstr(input, "> ");
+ int cursor_row=(pos+2)/COLS;
+ int end_row=(rl_end+2)/COLS;
+ // Figure out how much of the buffer to print to not scroll past the cursor
+ unsigned int eol=charstobytes(rl_line_buffer, rl_end, (cursor_row+2)*COLS-3); // -2 for cursor, -1 to avoid wrapping
+ waddnstr(input, rl_line_buffer, eol);
+
+ wmove(input, cursor_row==end_row && cursor_row>0, (pos+2)%COLS); // +2 for prompt
+ wrefresh(input);
+}
+
+void resizechat(int sig)
+{
+ struct winsize size;
+ ioctl(0, TIOCGWINSZ, &size);
+ if(size.ws_row<3){return;} // Too small, would result in negative numbers breaking the chat window
+ resize_term(size.ws_row, size.ws_col);
+ wresize(topic, 1, COLS);
+ wresize(chat, LINES-3, COLS);
+ wresize(input, 2, COLS);
+ mvwin(input, LINES-2, 0);
+ redrawwin(chat);
+ redrawwin(topic);
+ redrawwin(input);
+ wrefresh(chat);
+ wrefresh(topic);
+// wrefresh(input);
+ drawinput();
+}
+
+int main(int argc, char** argv)
+{
+ if(argc<3){execv("./tc_client", argv); return 1;}
+ setlocale(LC_ALL, "");
+ WINDOW* w=initscr();
+ signal(SIGWINCH, resizechat);
+ start_color();
+ cbreak();
+ noecho();
+ keypad(w, 1);
+ topic=newwin(1, COLS, 0, 0);
+ init_pair(1, COLOR_WHITE, COLOR_BLUE);
+
+ // Define colors mapped to ANSI color codes (at least the ones tc_client uses)
+ init_pair(2, COLOR_RED, 0);
+ init_pair(3, COLOR_GREEN, 0);
+ init_pair(4, COLOR_YELLOW, 0);
+ init_pair(5, COLOR_BLUE, 0);
+ init_pair(6, COLOR_MAGENTA, 0);
+ init_pair(7, COLOR_CYAN, 0);
+
+ wbkgd(topic, COLOR_PAIR(1)|' ');
+ chat=newwin(LINES-3, COLS, 1, 0);
+ scrollok(chat, 1);
+ input=newwin(2, COLS, LINES-2, 0);
+ scrollok(input, 1);
+ rl_initialize();
+ rl_callback_handler_install(0, gotline);
+ rl_bind_key('\x1b', escinput);
+ wprintw(input, "> ");
+ wrefresh(topic);
+ wrefresh(input);
+ int app_in[2];
+ int app_out[2];
+ pipe(app_in);
+ pipe(app_out);
+ if(!fork())
+ {
+ close(app_in[1]);
+ close(app_out[0]);
+ dup2(app_in[0],0);
+ dup2(app_out[1],1);
+ argv[0]="./tc_client";
+ execv("./tc_client", argv);
+ _exit(1);
+ }
+ close(app_in[0]);
+ close(app_out[1]);
+ to_app=app_in[1];
+ struct pollfd p[2]={{.fd=0, .events=POLLIN, .revents=0},
+ {.fd=app_out[0], .events=POLLIN, .revents=0}};
+ while(1)
+ {
+ poll(p, 2, -1);
+ if(p[1].revents) // Getting data from tc_client
+ {
+ p[1].revents=0;
+ char buf[1024];
+ size_t len=0;
+ while(len<1023)
+ {
+ if(read(app_out[0], &buf[len], 1)!=1){len=-1; break;}
+ if(buf[len]=='\r'||buf[len]=='\n'){break;}
+ ++len;
+ }
+ if(len==-1){break;} // Bad read
+ buf[len]=0;
+ if(!strncmp(buf, "Room topic: ", 12))
+ {
+ werase(topic);
+ waddstr(topic, &buf[12]);
+ wrefresh(topic);
+ }
+ waddstr(chat, "\n");
+ waddansi(chat, buf);
+ wrefresh(chat);
+ wrefresh(input);
+ continue;
+ }
+ if(!p[0].revents){continue;}
+ p[0].revents=0;
+ rl_callback_read_char();
+ drawinput();
+#if 0
+// TODO: move this into a function and also call it when the terminal is resized
+ werase(input);
+ unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
+
+ waddstr(input, "> ");
+ int cursor_row=(pos+2)/COLS;
+ int end_row=(rl_end+2)/COLS;
+ // Figure out how much of the buffer to print to not scroll past the cursor
+// unsigned int eol=cursor_row;// (pos+2)/COLS;
+ unsigned int eol=charstobytes(rl_line_buffer, rl_end, (cursor_row+2)*COLS-3); // -2 for cursor, -1 to avoid wrapping
+ waddnstr(input, rl_line_buffer, eol);
+
+ wmove(input, cursor_row==end_row && cursor_row>0, (pos+2)%COLS); // +2 for prompt
+ wrefresh(input);
+#endif
+ }
+ rl_callback_handler_remove();
+ endwin();
+ return 0;
+}
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index 90eec60..54a3ac5 100644
--- a/utilities/modbot/modbot.c
+++ b/utilities/modbot/modbot.c
@@ -127,7 +127,7 @@ void playnext(int x)
{
if(!waitskip)
{
- say(0, "Next video (http://youtube.com/watch?v=%s) is not yet approved by mods\n", queue.items[0].video);
+ say(0, "Next video (%s, %s) is not yet approved by mods\n", queue.items[0].video, queue.items[0].title);
unsigned int i;
for(i=1; i<queue.itemcount; ++i)
{
@@ -304,6 +304,10 @@ int main(int argc, char** argv)
break;
}
}
+ if(i==queue.itemcount)
+ {
+ say(pm, "I can't find any request by you, sorry\n");
+ }
}
else if(!strncmp(msg, "!wrongrequest ", 14))
{
@@ -354,7 +358,7 @@ int main(int argc, char** argv)
say(pm, "%u videos in queue\n", queue.itemcount);
}
}
- else if(!strcmp(msg, "!whorequested"))
+ else if(!strcmp(msg, "!requestedby"))
{
if(!playing){say(pm, "Nothing is playing\n");}
else{say(pm, "%s requested %s\n", requester, playing);}
@@ -391,6 +395,8 @@ int main(int argc, char** argv)
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");
}
else if(list_contains(&mods, nick)) // Mods-only commands
@@ -476,13 +482,15 @@ int main(int argc, char** argv)
{
getvidinfo(&space[1], "--get-id", vid, 256);
}else{strncpy(vid, playing, 1023); vid[1023]=0;}
- if(!vid[0]){say(pm, "Video not found, sorry\n");}
+ if(!vid[0]){say(pm, "Video not found, sorry\n"); continue;}
queue_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(playing && !strcmp(vid, playing)){say(0, "/mbc youTube\n");}
+ say(pm, "Marked '%s' as bad, it will not be allowed into the queue again. You can reverse this by !approve'ing the video by ID/link/name\n", vid);
+ playnext(0);
}
else if(!strcmp(msg, "!skip") || !strncmp(msg, "!skip ", 6))
{
@@ -511,7 +519,12 @@ int main(int argc, char** argv)
alarm(getduration(playing)-pos);
started=time(0)-pos;
}
- else if(!strcmp(msg, "/mbc youTube")){playnext(0);} // Video cancelled
+ else if(!strcmp(msg, "/mbc youTube")) // Video cancelled
+ {
+ list_del(&goodvids, playing); // manual /mbc is often used when !badvid should be used, so at least remove it from the list of approved videos
+ list_save(&goodvids, "goodvids.txt");
+ playnext(0);
+ }
else if(!strncmp(msg, "/mbsk youTube ", 14)) // Seeking
{
unsigned int pos=strtol(&msg[14], 0, 0)/1000;