last.c (6776B)
1 #define EXTERN 2 #define _GNU_SOURCE 3 #include <u.h> 4 #include <libc.h> 5 #include <auth.h> 6 #include <bio.h> 7 #include <mp.h> 8 #include <libsec.h> 9 #include <signal.h> 10 #include <string.h> 11 #include <thread.h> 12 #include "last.h" 13 14 static long endtime; 15 16 enum { 17 Rmsg, 18 Rresponse, 19 Rbase_path, 20 Rbase_url, 21 Rstream_url, 22 Ralbum, 23 Rartist, 24 Rartist_url, 25 Rsession, 26 Rstation, 27 Rstreaming, 28 Rtrack, 29 Rtrackduration, 30 }; 31 char *respnam[] = { 32 "msg", 33 "response", 34 "base_path", 35 "base_url", 36 "stream_url", 37 "album", 38 "artist", 39 "artist_url", 40 "session", 41 "station", 42 "streaming", 43 "track", 44 "trackduration", 45 }; 46 char *resp[nelem(respnam)]; 47 48 typedef struct Assoc Assoc; 49 struct Assoc { 50 int field; 51 char *str; 52 } junk[] = { 53 Rartist, "John Cage", 54 Rartist, "Morton Feldman", 55 Rartist, "Conlon Nancarrow", 56 Rartist, "Iannis Xenakis", 57 Rartist, "Olivier Messiaen", 58 Rartist, "Mauricio Kagel", 59 Ralbum, "Music Box", 60 0, 61 }; 62 63 char* 64 erespval(int i) { 65 66 if(resp[i] == nil) 67 sysfatal("expected response value %q is missing", 68 respnam[i]); 69 return strdup(resp[i]); 70 } 71 72 void 73 exitsall(char *err, ...) { 74 va_list ap; 75 76 if(err) { 77 va_start(ap, err); 78 err = vsmprint(err, ap); 79 va_end(ap); 80 print("threadexitsall(%s)\n", err); 81 } 82 threadexitsall(err); 83 } 84 85 void 86 msg(char *fmt, ...) { 87 va_list ap; 88 89 va_start(ap, fmt); 90 vfprint(1, fmt, ap); 91 va_end(ap); 92 print("> "); 93 } 94 95 int 96 Rconv(Fmt *f) { 97 int i; 98 99 i = va_arg(f->args, int); 100 assert(i < nelem(resp)); 101 return fmtprint(f, "%s", (resp[i] ? resp[i] : "unknown")); 102 } 103 104 int 105 Vconv(Fmt *f) { 106 VFmt *v; 107 108 v = va_arg(f->args, VFmt*); 109 return fmtvprint(f, (char*)v->fmt, v->args); 110 } 111 112 int 113 get(char *path, char *fmt, ...) { 114 va_list ap; 115 Biobuf *b; 116 char *s, *t, *uri; 117 char buf[1024]; 118 int i, n; 119 120 s = query(path, fmt); 121 va_start(ap, fmt); 122 uri = vsmprint(s, ap); 123 va_end(ap); 124 free(s); 125 126 n = strlen(uri); 127 if(n && uri[n-1] == '&') 128 uri[n-1] = '\0'; 129 130 for(i=0; i <= Rresponse; i++) 131 if(resp[i]) { 132 free(resp[i]); 133 resp[i] = nil; 134 } 135 136 if(debug['a'] && !debug['h']) 137 print("GET %q %s\n", host, uri); 138 do { 139 b = httpget(host, uri); 140 rerrstr(buf, sizeof buf); 141 }while(b == nil && strcasestr(buf, "interrupted")); 142 free(uri); 143 if(b == nil) { 144 resp[Rresponse] = smprint("get fails: %r"); 145 return 1; 146 } 147 148 if(debug['a']) 149 print("Attributes:\n"); 150 151 while((s = Brdline(b, '\n'))) { 152 s[BLINELEN(b)-1] = '\0'; 153 attr: 154 if(debug['a']) 155 print("\t%s\n", s); 156 t = strchr(s, '='); 157 if(t == nil) 158 continue; 159 *t++ = '\0'; 160 for(i=0; i < nelem(respnam); i++) 161 if(!strcmp(respnam[i], s)) { 162 if(resp[i]) 163 free(resp[i]); 164 resp[i] = strdup(t); 165 break; 166 } 167 } 168 169 n = Bread(b, buf, sizeof(buf)-1); 170 if(n > 0) { 171 buf[n] = '\0'; 172 s = buf; 173 goto attr; 174 } 175 176 Bterm(b); 177 return 0; 178 } 179 180 int 181 rpc(const char *cmd, const char *fmt, ...) { 182 VFmt v; 183 int i; 184 185 v.fmt = query(nil, fmt); 186 va_start(v.args, fmt); 187 i = get("%s/%s.php", "session=%h %V", basepath, cmd, session, &v); 188 va_end(v.args); 189 free(v.fmt); 190 return i; 191 } 192 193 void 194 erpc(const char *cmd, const char *fmt, ...) { 195 VFmt v; 196 197 v.fmt = query(nil, fmt); 198 va_start(v.args, fmt); 199 if(get("%s/%s.php", "session=%h %V", basepath, cmd, session, &v)) 200 sysfatal("rpc %s: %r\n", cmd); 201 va_end(v.args); 202 free(v.fmt); 203 } 204 205 int 206 skip(char *cmd) { 207 skipping = 1; 208 return rpc("control", "command=%h", cmd); 209 } 210 211 static void 212 printresp(void) { 213 char *s; 214 215 s = resp[Rresponse]; 216 print("Response: %s\n", s); 217 if(s && !strcmp(s, "FAILED")) 218 print(" Reason: %R\n", Rmsg); 219 } 220 221 void 222 getmeta(void) { 223 Assoc *a; 224 char *s; 225 long sec; 226 int n, i; 227 228 for(i=Rartist; i < nelem(resp); i++) { 229 free(resp[i]); 230 resp[i] = nil; 231 } 232 233 n = 0; 234 do { 235 if(n > 50) 236 exitsall("meta"); 237 erpc("np", ""); 238 }while(resp[Rstreaming] == nil || strcmp(resp[Rstreaming], "true")); 239 240 s = resp[Rtrackduration]; 241 sec = 0; 242 if(s) 243 sec = strtol(s, nil, 10); 244 endtime = time(0) + sec; 245 246 print("\n"); 247 print("Station: %R\n", Rstation); 248 print("Artist: %R <%R>\n", Rartist, Rartist_url); 249 print("Album: %R\n", Ralbum); 250 print("Track: %R\n", Rtrack); 251 print("Duration: %d:%02d\n", sec/60, sec%60); 252 print("Ends: (approx) %s", ctime(endtime)); 253 print("\n"); 254 print("> "); 255 256 label("Playing “%R” by %R", Rtrack, Rartist); 257 258 for(a=junk; a->str; a++) 259 if(resp[a->field] && cistrstr(resp[a->field], a->str)) { 260 print("Junk %s; banning.\n", respnam[a->field]); 261 skip("ban"); 262 printresp(); 263 break; 264 } 265 } 266 267 static void 268 userinfo(char **user, char **pass) { 269 static char buf[MD5dlen*2]; 270 uchar digest[MD5dlen]; 271 UserPasswd *up; 272 int i; 273 274 up = auth_getuserpasswd(auth_getkey, "proto=pass dom=last.fm role=client"); 275 if(up == nil) 276 sysfatal("no username/password: %r"); 277 278 md5((uchar*)up->passwd, strlen(up->passwd), digest, nil); 279 for(i=0; i < MD5dlen; i++) 280 sprint(buf+2*i, "%02x", digest[i]);; 281 282 *user = strdup(up->user); 283 *pass = buf; 284 memset(up->passwd, 0, strlen(up->passwd)); 285 free(up); 286 } 287 288 void 289 threadmain(int argc, char *argv[]) { 290 Biobuf in; 291 char *uri, *s, *q, *user, *pass; 292 long n; 293 294 ARGBEGIN{ 295 default: 296 n = ARGC(); 297 if((ulong)n < 128) 298 debug[n]++; 299 break; 300 }ARGEND; 301 302 fmtinstall('V', Vconv); 303 fmtinstall('R', Rconv); 304 quotefmtinstall(); 305 306 signal(SIGCHLD, SIG_IGN); 307 notifyoff("sys: child"); 308 309 label("Wait..."); 310 311 httpinit(); 312 313 userinfo(&user, &pass); 314 315 host = "ws.audioscrobbler.com"; 316 if(get("/radio/handshake.php", "version=1.0.1 platform=Plan+9 username=%h passwordmd5=%h", 317 user, pass)) 318 sysfatal("handshake fails: %r"); 319 320 host = erespval(Rbase_url); 321 basepath = erespval(Rbase_path); 322 session = erespval(Rsession); 323 324 uri = erespval(Rstream_url); 325 if(parseuri(uri, &streamhost, &streampath)) 326 sysfatal("Bad stream URI"); 327 328 initplayer(); 329 330 label("Stopped."); 331 Binit(&in, 0, OREAD); 332 while(print("> "), Bgetc(&in) != Beof) { 333 Bungetc(&in); 334 s = Brdline(&in, '\n'); 335 if(s == nil) 336 continue; 337 s[BLINELEN(&in)-1] = '\0'; 338 q = tok(&s); 339 if(!strcmp(q, "start")) { 340 newstream(); 341 }else 342 if(!strcmp(q, "stop")) { 343 endstream(); 344 }else 345 if(!strcmp(q, "info")) { 346 getmeta(); 347 }else 348 if(!strcmp(q, "skip") || !strcmp(q, "ban")) { 349 label("Skipping..."); 350 skip(q); 351 printresp(); 352 }else 353 if(!strcmp(q, "love")) { 354 rpc("control", "command=love"); 355 printresp(); 356 }else 357 if(!strcmp(q, "artist")) { 358 rpc("adjust", "url=%h%h", "lastfm://artist/", s); 359 printresp(); 360 }else 361 if(!strcmp(q, "tag")) { 362 rpc("adjust", "url=%h%h", "lastfm://globaltags/", s); 363 printresp(); 364 }else 365 if(!strcmp(q, "station")) { 366 rpc("adjust", "url=%,h", s); 367 printresp(); 368 }else 369 if(!strcmp(q, "time")) { 370 n = endtime - time(0); 371 print("Time left: (abbrox) %d:%02d\n", n/60, n%60); 372 print("Ends: (approx) %s", ctime(endtime)); 373 }else 374 if(!strcmp(q, "help")) { 375 print("Commands: start, stop, skip, love, ban, info, time, tag <tag>, artist <artist>, station <station>\n"); 376 }else if(q[0] != 0) 377 print("?\n"); 378 } 379 380 exitsall(nil); 381 } 382