last

git clone git://oldgit.suckless.org/last/
Log | Files | Refs

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