#!/usr/bin/env io # BSD License # Copyright (c) 2008, Offline Demo, LLC # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # * Neither the name of the Offline Demo, LLC nor the names of # its contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. Queue := List clone do( appendUnseenFriends := method(user, user friends foreach (i, v, if (Queue map(u, u screenName) contains(v) not, friend := User clone friend fill(v, user) Queue append(friend) ) ) ) seek := method(screenName, Queue foreach(i, user, if (user friends map(asLowercase) contains(screenName asLowercase), user lineage reverse map(u, u screenName) append(screenName) println ) if (user friends size > 0 and user lineage size < 5, appendUnseenFriends(user) ) ) ) ) Queue clone := Queue # makes Queue a singleton User := Object clone do( newSlot("screenName") newSlot("parent") newSlot("friends") fill := method(s, p, screenName = s asMutable lowercase parent = p friends = List clone retrieve_friends := method(n, if (n isNil, n = 1) f := File with("data/#{screenName}-#{n}.xml" interpolate) xml := if(f exists, f openForReading readStringOfLength(f size), Lobby wait(waitInterval) u := URL with("http://twitter.com/statuses/friends/#{screenName}.xml?lite=true&page=#{n}" interpolate) b := u fetch if (b isError not and u readHeader? containsSeq("200 OK"), f openForUpdating write(b) close b, "error - retrieving user: #{screenName} (page #{n}) - status #{u readHeader split at(1)}" interpolate println "" ) ) SGML xml asXML elementsWithName("screen_name") map(subitems at(0) asString) ) n := 1 while (friends size % 100 == 0 and friends size != friends appendSeq(retrieve_friends(n)) size, n = n + 1) ) lineage := method(ancestors, if (ancestors isNil, ancestors = list(self)) if (ancestors last parent, ancestors append(ancestors last parent) lineage(ancestors), ancestors ) ) ) if(System args size >= 3, Directory with("data") createIfAbsent waitInterval := System args at(3) ?asNumber or 60 user := User clone user fill(System args at(1)) Queue append(user) Queue seek(System args at(2)), "Usage: #{System args at(0)} fromUserName toUserName [waitInterval]" interpolate println )