MakerLab Blog » twitter http://blog.makerlab.com Go on, be curious Thu, 14 Mar 2013 06:30:21 +0000 en-US hourly 1 http://wordpress.org/?v=3.9.15 @kissmehere and there! and there! and there! Kissing Booths FTW! http://blog.makerlab.com/2009/04/kissmehere_silly_a_new_twitterbot/ http://blog.makerlab.com/2009/04/kissmehere_silly_a_new_twitterbot/#comments Mon, 13 Apr 2009 22:19:32 +0000 http://blog.makerlab.com/?p=675 makerlab we were talking about dating sites a bit - commenting on how strange it was that they didn't leverage social networks. For fun today I threw a fun idea together as a test of how to make dating more social. It isn't terribly serious but perhaps amusing. I like to combine talk with praxis. Here it is:]]> Last night at makerlab we were talking about dating sites a bit – we had some fun thinking about the different ways that we could imagine designing a simple tool for twitter. We decided we wanted it to be really really simple.

Like REALLY REALLY simple. Like this:

twitter.com/kissmehere

Kinda like a kiss. Yeah, just like that. Like a kissing booth! JUST like a Kissing Booth!

kisses!

kisses!

The way this all works is that when you send a message to @kissmehere on twitter and you include the name of some people, it will send a kiss to all those people. For example:

@kissmehere go kiss @paigesaez @zephoria @soycamo @semaphoria @anselm

Kissmehere is no prude, you can kiss more than one person at the same time – or kiss only one person – it is up to you.

Kissmehere maps your kisses too!

MakerLab Kiss Map!

@kissmehere

@kissmehere

How did I build it? This is just a riff on the same twitter code I have been using before. There are a few twists. First we have some pre-amble and we have a geocoding engine – thanks MetaCarta!!!:


require 'rubygems'
require 'dm-core'
require 'twitter'
require 'net/smtp'
require 'net/http'
require 'uri'
require 'json'
require 'dm-core'

#
# passwords
#
TWITTER_USER_NAME = "kissmehere"
TWITTER_PASSWORD = ""
METACARTA_USERID = ""
METACARTA_PASSWORD = ""
METACARTA_KEY = ""

#
# a very very nice metacarta utility to brute force discover location in text
#
def geolocate(location)
  location = URI.escape(location, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
  # location = URI.escape(location)
  host = "ondemand.metacarta.com"
  path = "/webservices/GeoTagger/JSON/basic?version=1.0.0"
  path = "#{path}&doc=#{location}"
  data = {}
  begin
    req = Net::HTTP::Get.new(path)
    req.basic_auth METACARTA_USERID, METACARTA_PASSWORD
    http = Net::HTTP.start(host)
    #if response.is_a?(Net::HTTPSuccess)
      response = http.request(req)
      puts response.body
      data = JSON.parse(response.body)
    #end
  rescue Timeout::Error
    # DO SOMETHING WISER
    return 0,0
  rescue
    return 0,0
  end
  begin
    lat = data["Locations"][0]["Centroid"]["Latitude"]
    lon = data["Locations"][0]["Centroid"]["Longitude"]
    return lat,lon
  rescue
  end
  return 0,0
end

We have a simple data model as usual to track our activity… again using datamapper which is a favorite of mine.


#
# Only send out 10 tweets at a time
#
twittercap = 10

#
# Grab a database
#
DataMapper.setup(:default, {
    :adapter  => 'postgres',
    :database => "kissmehere",
    :username => '',
    :password => '',
    :host     => 'localhost'
})

#
# here is our schema
#
class Kiss
  include DataMapper::Resource
  property :id,          Integer, :serial => true
  property :provenance,  Text
  property :uuid,        Text
  property :title,       Text
  property :link,        Text
  property :description, Text
  property :screenname,  Text
  property :userid,      Text
  property :location,    Text
  property :lat,         Float
  property :lon,         Float
  property :secret,      Integer, :default => 0
  property :friended,    Integer, :default => 0
  property :kissed_at,   DateTime
  property :created_at,  DateTime
end


We have the usual twitter gem code to peek at the twitter state. I am really starting to wonder how the heck twitter even stays up with the amount of traffic it is getting… In any case mine is not to worry but to do!


#
# Remember kiss requests
#
twitter = Twitter::Base.new(TWITTER_USER_NAME, TWITTER_PASSWORD )
twitter.replies().each do |twit|
  uuid = "#{twit.id}"
  kiss = Kiss.first(:provenance => "twitter", :uuid => uuid)
  next if kiss
  secret = 0
  secret = 1 if twit.text[/ secret/] != nil
  lat = 0
  lon = 0
  if twit.user.location && twit.user.location.length > 1
    lat,lon = geolocate(twit.user.location)
  end
  kiss = Kiss.create(
             :provenance => "twitter",
             :uuid => uuid,
             :title => twit.text,
             :link => nil,
             :description => nil,
             :screenname => twit.user.screen_name,
             :userid => twit.user.id,
             :location => twit.user.location,
             :lon => lon,
             :lat => lat,
             :secret => secret
          )
  kiss.save
  puts "Saved a kiss on twitter! #{kiss.userid} #{kiss.title} #{kiss.lat} #{kiss.lon}"
end



Next we want to respond to kisses in an intelligent way; telling everybody, friending new friends and all that kind of fun stuff.


#
# Pass new kisses onwards ( only do twittercaps worth )
#
@kisses = Kiss.all(:order => [:created_at.desc],
                   :limit => twittercap,
                   :kissed_at => nil
              ).each do |kiss|

  # tease each kiss apart for multiple receivers
  kisses = kiss.title.scan(/\@\w+/)
  kisses.each do |luckyduck|
    next if luckyduck == "@kissmehere"
    if kiss.secret == 0
      kiss.link = "http://twitter.com/#{kiss.screenname}/statuses/#{kiss.uuid}"
      gossip = "#{luckyduck} got a kiss from @#{kiss.screenname} - see #{kiss.link} "
      # if kiss.lat != 0 && kiss.lon != 0
      #  gossip = " - #{gossip} near #{kiss.location}"
      # end
    else
      kiss.link = nil
      gossip = "@#{luckyduck} got a kiss from an anonymous admirer!"
    end
    kiss.description = gossip
    result = twitter.post(gossip)
    puts "Told everybody #{result} of #{gossip}"
  end
  if kisses.length == 0
    puts "No love from #{kiss.screenname}"
  end
  kiss.kissed_at = DateTime.now
  kiss.save

  # friend everybody - could improve this
  begin
    twitter.create_friendship(kiss.screenname)
  rescue
  end
  kisses.each do |luckyduck|
    begin
      #if twitter.friendship_exists(TWITTER_USER_NAME,luckyduck)
      twitter.create_friendship(luckyduck)
    rescue
    end
  end

end

Finally we write out an RSS feed for Google Maps – thanks @ajturner for the quick tip. I wasn’t able to get ruby rss maker to do anything useful such as allow me to specify custom namespaces for the geo:lat and geo:long attributes so I wrote everything by hand! By doing this we can then make a map page which has all the kisses on it just for fun. I guess I won’t show this blob because it breaks the layout engine in wordpress… I will link to the original file however at agent.txt

That’s it. Have fun out there in the twitter verse!

]]>
http://blog.makerlab.com/2009/04/kissmehere_silly_a_new_twitterbot/feed/ 2
Iraq Deaths TwitterBot http://blog.makerlab.com/2009/04/iraq-deaths-twitterbot/ http://blog.makerlab.com/2009/04/iraq-deaths-twitterbot/#comments Thu, 02 Apr 2009 18:03:17 +0000 http://blog.makerlab.com/?p=668 We’ve posted an update to our Iraq Deaths agent at http://twitter.com/iraqdeaths . Here I’m going to journal and document the work involved in making this actually work.

At a high level this agent makes a daily post to twitter to broadcast any deaths reported by the Iraq War Bodycount database. I should mention that without their efforts at keeping and surfacing these records none of this would be possible. This kind of work itself is emotionally challenging and I want to applaud them.

Design-wise this was a project that Paige and I thought up and were excited by. We did it in a morning while we were both supposed to be doing other work – but the fact is it resonated with work we both care about.  Speaking for myself at Meedan we’ve been looking for ways to help social networks bridge the barriers of language and I saw this as a way to contribute to a project that helped people keep attention on something that is normally invisible.  For me this was a rewarding project because I enjoy removing the technical boundary between design and implementation. Often I like to work with designers to put real muscle underneath their vision.

The technical implementation consists of three stages

  1. collection
  2. analysis
  3. publishing

In the collections stage we talk to the iraq war bodycount database – pulling the entire corpus of deaths and then plucking them out of their csv structured text – like so:

require 'net/http'
require 'rubygems'
require "fastercsv"
url = "http://www.iraqbodycount.org/database/download/ibc-individuals"
data = Net::HTTP.get_response(URI.parse(url)).body
puts "fetched"
@results = FasterCSV.parse(data)
@deaths = []
inside_header = true
@results.each do |death|
 if death[0] == "IBC code"
   inside_header = false
 elsif inside_header == false
   @deaths << death
 end
end



After this stage we go and add any new data to our own database. In this way we keep a running track of any changes and can act only on changes rather than on all data I first attempted to use sqlite3 but then ended up using datamapper - like so:

require 'rubygems'
require 'dm-core'
DataMapper.setup(:default, {
   :adapter  => 'postgres',
   :database => "endiraqwar",
   :username => 'endiraqwar',
   :host     => 'localhost'
})
class Death
 include DataMapper::Resource
 property :id,         Integer, :serial => true
 property :code,       String
 property :name,       Text
 property :age,        Text
 property :sex,        Text
 property :marital,    Text
 property :parental,   String
 property :earliest,   DateTime
 property :latest,     DateTime
 property :location,   Text
 property :created_at, DateTime
 property :posted,     DateTime, :default => nil
end
# DataMapper.auto_migrate!
@deaths.each do |death|
 if Death.first(:code => death[0] )
   puts "We already found this death #{death[1]} #{death[0]} so not saving"
   next
 end
 # take a second to convert the date phrase into a machine date
 death[6] = DateTime.parse(death[6])
 death[7] = DateTime.parse(death[7])
 record = Death.new(
             :code => death[0],
             :name => death[1],
             :age => death[2],
             :sex => death[3],
             :marital => death[4],
             :parental => death[5],
             :earliest => death[6],
             :latest => death[7],
             :location => death[8]
          )
 puts "recording the passing of #{record.name} at #{record.earliest} and #{record.code}"
 record.save
end



The last phase is to report the actual deaths. We rely on the twitter gem to do this - I find I am using this gem more and more and it is quite convenient - like so:

twittercap = 50 # twitter this many posts max
require 'twitter'
twitter = Twitter::Base.new("iraqdeaths",secret_password)
@deaths = Death.all(:order => [:earliest.desc], :limit => twittercap)
@copyofdeaths = []
@deaths.each do |death|
 @copyofdeaths << death
end
@copyofdeaths.reverse.each do |death|
 # publish deaths that are new
 next if death.posted != nil
 result = twitter.post("#{death.name}, #{death.age}, #{death.sex}, #{death.marital}, #{death.parental} killed on #{death.earliest.strftime("%d %b %Y")} at L:#{death.location}")
 # remember that we already published this death
 death.posted = DateTime.now
 death.save
 puts "posted the death of #{death.name} #{death.code}"
end



I try to be very careful to never update my own understanding of the database record until I am absolutely certain that Twitter has been updated. Even if my agent crashes I want it to crash in a way that doesn't spray garbage all over twitterspace.

Overall, as you can see, the high aspiration of building something that makes a statement is connected to real metal underneath. We're grateful to the http://www.iraqbodycount.org organization for making this possible.

]]>
http://blog.makerlab.com/2009/04/iraq-deaths-twitterbot/feed/ 0
Measuring the Speed of Social Networks http://blog.makerlab.com/2008/11/measuring-the-speed-of-social-networks/ http://blog.makerlab.com/2008/11/measuring-the-speed-of-social-networks/#comments Sun, 30 Nov 2008 12:16:11 +0000 http://blog.makerlab.com/?p=256 We can estimate the relative speed of social networks based on profile to response ratio.

Twitter has a high profile to response ratio. Each environment and person’s profile is compressed for light transit. Myspace has more social geography. Each user has more visual and textual space. Each space is capable of being altered in size, shape, color, and there are embeddable objects.

With Twitter, there are limits on how one can augment another’s space. There is little tolerance for spamming, and much data transparency. The subscribe function limits what one can write on one’s “wall”.

This makes Twitter less iconic and faster. With profiles compressed for easy flow, data becomes more important than image. Some Twitter accounts can actually get away without even including an avatar of themselves if they provide a good data stream.

]]>
http://blog.makerlab.com/2008/11/measuring-the-speed-of-social-networks/feed/ 0