
You probably won't notice a big difference (unless you have 1000+ viewers on your channel), but rest assured we'll be ready when you become the lonelygirl15 of Justin.tv!
Justin.tv's web code is written in Ruby. I've found myself using the same collection idioms over and over, so I've abstracted several of them into a file called shorthand.rb.
Most of our web code consists of manipulating collections in one form or another, which is probably why all the shorthand methods are for that. I'm particular proud of my % operator, which I believe is a specialized mapcar in spirit. Without further ado, code!
module Enumerable
def %(field)
map {|o| o.send(field)}
end
end
class Hash
def keys_sorted_by_value(options = Hash.new, &block)
limit = options[:limit] || size
offset = options[:offset] || 0
sorted = map.sort_by do |key, value|
if block
yield(key, value)
else
value
end
end
sorted.reverse! if options[:reverse]
sorted.map {|key, value| key}.slice(offset, limit) || Array.new
end
def hmap
h = dup
keys.each do |k|
h[k] = yield h[k]
end
h
end
def to_params
map {|k, v| "#{k}=#{CGI::escape v.to_s}"}.join("&")
end
end
class Array
def hash_by(field)
h = Hash.new
each {|o| h[o.send(field)] = o}
h
end
def center_first
centered = Array.new
last_pushed = false
each do |x|
centered.push(x) unless last_pushed
centered.unshift(x) if last_pushed
last_pushed = !last_pushed
end
centered
end
def random_subset(n=size)
shuffle.slice(0, n)
end
def shuffle
sort_by { rand }
end
def divide
evens = Array.new
odds = Array.new
each_with_index do |x, i|
if i % 2 == 0
evens << x
else
odds << x
end
end
[evens, odds]
end
def remove!(item)
reject! {|x| x == item}
end
def remove(item)
reject {|x| x == item}
end
end
We launched the new justin.tv search engine a few days ago. Now I'm excited to announce that it has an API you can use in your own programs.
We've based the API on some very familiar open standards - basically HTTP and JSON, so using it should be a piece of cake from just about any language. To see an example in Common Lisp, scroll to the end of this post.
To use our search API, you just need to send an HTTP request to http://search.justin.tv:6979/ with a bunch of parameters. Here's a complete list of the parameters that are available right now:
q | The search query (required). Keywords are separated by url-encoded spaces ('+' or '%20'). Anything non-alphanumeric is ignored. |
sort-by | One of 'bestmatch' (default), 'newest', 'oldest', 'mostviews'. |
page | Page number in results set, default 1. |
results-per-page | Maximum 100, minimum 10, default 10. |
show-archives | Return video archives. 'yes', 'true', 'on' all do the same thing. |
show-live-broadcasters | Return users who are currently broadcasting. 'yes', 'true', 'on' all do the same thing. |
show-offline-broadcasters | Return users who are not currently broadcasting. 'yes', 'true', 'on' all do the same thing. |
If none of the above three are specified, they are all assumed to be 'true'. | |
broadcasters | Only return results from a list of named broadcasters, e.g. 'b1,b2,b3'. |
encode-as | One of 'html-fragment' (default), 'html-page', 'json'. |
So let's say we want to build a small application, using the search api, that alerts us whenever a new video clip is available that has something to do with cats. Here's a query that would be a good starting point for an application like that:
?q=cat&sort-by=newest&show-archives=true&encode-as=json
Let's pull that apart and look at what each piece does.
q=cat | We're interested in search results whose metadata contain the word "cat". |
sort-by=newest | We want the most recently produced results. |
show-archives=true | We do want video archives in our results. Note that show-live-broadcasters and show-offline-broadcasters will both default to 'false' because we've set show-archives to 'true'. |
encode-as=json | The results set should be encoded using json. |
Let's send that query to search.justin.tv and see what we get back:
http://search.justin.tv:6979/?q=cat&sortby=newest&show-archives=true&encode-as=json
returns:
[{"type": "video_archive", "broadcaster": "ggjeffy", "id": 28144, "title": "Cat doing cat stuffs", "start_time": 1186542932, "duration": 180}, {"type": "video_archive", "broadcaster": "nekomimi_lisa", "id": 39175, "title": "Nekomimi Cat Doing The Cat Dance", "start_time": 1191805139, "duration": 108}, {"type": "video_archive", "broadcaster": "nekomimi_lisa2", "id": 9832, "title": "CAT FIGHT!!", "start_time": 1185954224, "duration": 180}, {"type": "video_archive", "broadcaster": "nekomimi_lisa2", "id": 9818, "title": "cat trying to hump blanket", "start_time": 1185939617, "duration": 180}, {"type": "video_archive", "broadcaster": "ashleymarie", "id": 36553, "title": "Sister Cat Fight Part 1", "start_time": 1190060150, "duration": 80}, {"type": "video_archive", "broadcaster": "ibrbigottopee", "id": 34698, "title": "I thought i saw a putty cat", "start_time": 1189149636, "duration": 66}, {"type": "video_archive", "broadcaster": "ashleymarie", "id": 36555, "title": "Sister Cat Fight Part 2", "start_time": 1190060349, "duration": 66}, {"type": "video_archive", "broadcaster": "audratv", "id": 39679, "title": "Attack of the Fuzzy Cat Part 2", "start_time": 1191985459, "duration": 180}, {"type": "video_archive", "broadcaster": "audratv", "id": 39656, "title": "Cat Attack", "start_time": 1191980666, "duration": 180}, {"type": "video_archive", "broadcaster": "xk3ll3yx", "id": 40879, "title": "Cat on Head!", "start_time": 1192431728, "duration": 71}]
Now we need to decode that blob of json. Fortunately there's a library to do that for every language under the sun (look on the json page for yours).
So let's see how we would start writing that feline-video-feed in my favorite language, Common Lisp. First we need libraries for doing the http request and the json decoding. trivial-http and cl-json are more than good enough:
(require :trivial-http)
(require :json)
Let's write a "find-cats" function, which will send the http request and return the results as a string:
(defparameter *url*
"http://search.justin.tv:6979/?q=cat&sort-by=newest&show-archives=true&encode-as=json")
(defun find-cats ()
(let ((stream (first (last (trivial-http:http-get *url*))))
(json nil)
(line nil))
(loop while (setf line (read-line stream nil nil)) do
(push line json))
(apply #'concatenate 'string (nreverse json))))
Now we just need to call that function periodically, parse the json, and print any new cat videos:
(defun cats-alert ()
(let ((known-cat-videos (make-hash-table :test #'equalp)))
(loop
(let ((cats (json:decode-json-from-string (find-cats))))
(dolist (cat cats)
(unless (gethash cat known-cat-videos)
(setf (gethash cat known-cat-videos) t)
(format t "New cat video!~%~A~%http://www.justin.tv/~A/~A~%~%"
(rest (assoc 'title cat))
(rest (assoc 'broadcaster cat))
(rest (assoc 'id cat)))
(force-output))))
(sleep 600))))
That's it, we're done! Here's an example of the program's output:
CL-USER> (cats-alert)
New cat video!
Get the cat butt out of the way!
http://www.justin.tv/fistonet/44923
New cat video!
cat vs dog
http://www.justin.tv/midolgirl/44656
New cat video!
scared cat!
http://www.justin.tv/bobtv/43959
New cat video!
Bob scares the cat! lol
http://www.justin.tv/bobtv/43970
New cat video!
Cat fight. Or something.
http://www.justin.tv/ashleyisawesome/43814
New cat video!
Cat on Head - Part II (With New Web Cam)
http://www.justin.tv/shamrox/43719
New cat video!
Cat fight
http://www.justin.tv/nerdette/42933
New cat video!
Dear cat in the middle of the road, i hate you.
http://www.justin.tv/icantstopiwontstop/42878
New cat video!
Jasmine Playing... (& the amazing flying cat-jumps) TOO CUTE!
http://www.justin.tv/lizzymayhem/42798
New cat video!
KT, the cat, running around acting strange, then craps on the floor
http://www.justin.tv/nekomimi_lisa/43225
We can't wait to see what you do with the APIs we're developing. Email me (bill at justin dot tv) if you have something cool to show off, or if you have any questions or comments.