$ git clone http://tcclient.ion.nu/tc_client.git
commit 0597e0d63b7fd1546d5247167463f56890b684bc
Author: Alicia <...>
Date:   Tue Apr 7 06:49:01 2015 +0200

    Version 0.18

diff --git a/ChangeLog b/ChangeLog
index 81e809c..179ed1f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+0.18:
+This release only affects modbot:
+Switched to using youtube-dl for getting IDs (including proper searching)
+Fixed and clarified the !help text.
+When a video is next to be played and has not been approved for 2 minutes, move the first approved video to the front instead of moving the first video to the back.
+Added '!approve next' which approves the first thing in queue that is not yet approved.
+Added !time to get the amount of time left for the current video (mostly for debugging)
+Handle /mbsk (seeking)
 0.17:
 irchack: translate tc_client's "No such nick" to IRC's 401 (No such nick/channel)
 modbot: mark manually started videos as playing.
diff --git a/Makefile b/Makefile
index 2e357fe..25bad9c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.17
+VERSION=0.18
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
 LIBS=-g3 $(shell curl-config --libs)
 ifneq ($(wildcard config.mk),)
diff --git a/modbot.c b/modbot.c
index 36d5ddf..2f25305 100644
--- a/modbot.c
+++ b/modbot.c
@@ -120,22 +120,12 @@ void list_save(struct list* list, const char* file)
   close(f);
 }
 
-void list_movetoback(struct list* list, const char* item)
+void list_movetofront(struct list* list, unsigned int pos)
 {
-  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
-{
-  char* x;
-  if((x=strstr(string, "?v=")))
-  {
-    return &x[3];
-  }
-  return string; // last resort: assume it's already an ID
+  if(pos>=list->itemcount){return;}
+  char* move=list->items[pos];
+  memmove(&list->items[1], list->items, sizeof(char*)*pos);
+  list->items[0]=move;
 }
 
 void say(const char* pm, const char* fmt, ...)
@@ -157,7 +147,7 @@ void say(const char* pm, const char* fmt, ...)
   write(tc_client, buf, strlen(buf));
 }
 
-unsigned int getduration(const char* vid)
+void getvidinfo(const char* vid, const char* type, char* buf, unsigned int len)
 {
   int out[2];
   pipe(out);
@@ -165,19 +155,27 @@ unsigned int getduration(const char* vid)
   {
     close(out[0]);
     dup2(out[1], 1);
-    write(1, ":", 1);
-    execlp("youtube-dl", "youtube-dl", "--get-duration", "--", vid, (char*)0);
+    execlp("youtube-dl", "youtube-dl", "--default-search", "auto", type, "--", vid, (char*)0);
     perror("execlp(youtube-dl)");
     _exit(1);
   }
   wait(0);
   close(out[1]);
-  char timebuf[128];
-  int len=read(out[0], timebuf, 127);
-  if(len<2){printf("Failed to get video duration using youtube-dl, assuming 60s\n"); return 60;} // If using youtube-dl fails, assume all videos are 1 minute long
-  timebuf[len]=0;
+  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)
+{
+  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
   // 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]);}
@@ -193,7 +191,7 @@ unsigned int getduration(const char* vid)
   return len;
 }
 
-char waitskip=0;
+unsigned int waitskip=0;
 void playnextvid()
 {
   waitskip=0;
@@ -220,17 +218,16 @@ void playnext(int x)
       {
         if(list_contains(&goodvids, queue.items[i]))
         {
-          waitskip=1;
+          waitskip=i;
           alarm(120);
           break;
         }
       }
       return;
     }else{
-      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);
+      list_movetofront(&queue, waitskip);
+      waitskip=0;
     }
   }
   playnextvid();
@@ -309,13 +306,22 @@ int main(int argc, char** argv)
         }
         if(!strncmp(msg, "!request ", 9))
         {
-          char* vid=getyoutube(&msg[9]);
+          char title[256];
+          char vid[256];
+          getvidinfo(&msg[9], "--get-id", vid, 256);
+          if(!vid[0]){say(pm, "No video found, sorry\n"); continue;} // Nothing found
+          getvidinfo(vid, "--get-title", title, 256);
           printf("Requested ID '%s' by '%s'\n", vid, nick);
           // Check if it's already queued and mention which spot it's in
           int pos;
           if((pos=list_getpos(&queue, vid))>-1)
           {
-            say(pm, "That video is already in queue (number %i)\n", pos);
+            say(pm, "Video '%s' is already in queue (number %i)\n", title, pos);
+            continue;
+          }
+          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))
@@ -327,16 +333,11 @@ int main(int argc, char** argv)
             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);
           }
           if(!list_contains(&goodvids, vid))
           {
-            say(pm, "Video '%s' is added to the queue but will need to be approved by mods\n", vid);
+            say(pm, "Video '%s' (%s) is added to the queue but will need to be approved by mods\n", vid, title);
           }
           else if(!playing){playnext(0);}
           else{say(pm, "Added to queue\n");}
@@ -367,6 +368,12 @@ 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");
@@ -379,13 +386,15 @@ int main(int argc, char** argv)
           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");
+          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 <link> = mark the specified video as okay\n");
+          say(nick, "!approve next   = mark the next not yet approved 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");
+          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
         {
@@ -415,8 +424,21 @@ int main(int argc, char** argv)
           }
           else if(!strncmp(msg, "!approve ", 9))
           {
-            char* vid=getyoutube(&msg[9]);
-            if(!vid[0]){vid=playing; if(!vid){continue;}}
+            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{
+              getvidinfo(vid, "--get-id", vidbuf, 256);
+              vid=vidbuf;
+            }
             list_add(&goodvids, vid);
             list_del(&badvids, vid);
             list_save(&goodvids, "goodvids.txt");
@@ -447,7 +469,12 @@ int main(int argc, char** argv)
             playing=strdup(vid);
             alarm(getduration(vid));
           }
-          else if(!strcmp(msg, "/mbc youTube")){playnext(0);} // TODO: handle /mbsk (seek) too?
+          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);
+          }
         }
       }else{ // Actions
         if(!strncmp(space, " changed nickname to ", 21))