Justin Collins wrote:
> Daniel Moore wrote:
>> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>>
>> The three rules of Ruby Quiz:
>>
>> 1.  Please do not post any solutions or spoiler discussion for this
>> quiz until 48 hours have elapsed from the time this message was
>> sent.
>>
>> 2.  Support Ruby Quiz by submitting ideas and responses
>> as often as you can!
>> Visit: http://rubyquiz.strd6.com/suggestions
>>
>> 3.  Enjoy!
>>
>> Suggestion:  A [QUIZ] in the subject of emails about the problem
>> helps everyone on Ruby Talk follow the discussion.  Please reply to
>> the original quiz message, if you can.
>>
>> RSS Feed: http://rubyquiz.strd6.com/quizzes.rss
>>
>> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
>>
>> ## Twitter Personalities (#208)
>>
>> Merhaba Rubyists,
>>
>> This week's quiz is to create a program that will generate messages
>> 140 characters in length. There primary use will be to create a
>> Twitter "personality". At the end of the quiz period these
>> "personalities" will be unleashed on the internet and we'll see how
>> they do in the wild.
>>
>> The programs will consist of two parts: a component for interacting
>> with Twitter, and a top secret "personality" module.
>>
>> For the Twitter interface component there will be no no-spoiler
>> period. Please feel encouraged to discuss different libraries or
>> methods on the mailing list. Let's all work together to find the best
>> interface.
>>
>> The "personality" component can take any inputs and will produce a 140
>> character message when called. The "personality" may remember state.
>> The no-spoiler period applies for the "personality" component; please
>> save them until everyone has had a chance to consider their own
>> implementations.
>>
>>
>> Have Fun!
>>   
>
> Posting to Twitter, I found, was pretty straightforward using the 
> Twitter4r gem, although it only worked for me under Ruby 1.9:
>
> require 'twitter'
> client = Twitter::Client.new :login => "myname", :password => 
> "mypassword"
> client.status :post, "Working on Ruby Quiz."
>
>
> I hope that helps anyone who might have been hesitant because of the 
> Twitter interface part.
>
> -Justin
>


So, here is my full solution, which just pulls transcriptions of 
Dijkstra's notes and tries to extract decent quotes. Also attempts to 
find relavent replies. Works most of the time. You can see some results 
at http://twitter.com/ewdbot and get a nicer-looking version from 
http://gist.github.com/125781

#A little Twitter thing to post random quotes from Edsger Dijkstra

require 'yaml'
require 'open-uri'
require 'twitter'  #twitter4r gem
require 'hpricot'

class DijkstraQuote
    class << self

        #Match the website's naming scheme
        def random_ewd
            (rand(1289) + 30).to_s.rjust(4, "0")
        end

        #Get a random quote
        def get_quote
            quote = nil
            while quote.nil? do
                quote = fetch random_ewd
            end

            quote
        end

        #Get a random quote from the specified set of notes
        def fetch ewd

            #Check if we've already retrieved this set of notes
            if File.exists? "ewd#{ewd}"
                quotes = YAML.load_file "ewd#{ewd}"
                quotes[rand quotes.length]
            else
                $stderr.puts "Fetching EWD#{ewd}..." if $DEBUG

                #Fetch a transcript of Dijkstra's notes
                begin
                    file = 
open("http://www.cs.utexas.edu/~EWD/transcriptions/EWD#{ewd[0,2]}xx/EWD#{ewd}.html") 
{ |f| Hpricot(f) }
                rescue OpenURI::HTTPError
                    $stderr.puts "Not found" if $DEBUG
                    return nil
                end

                text = file.to_plain_text

                #Pick some sentences that seem good
                lines = text.split(/\.(?:\s+|\n+)/).map do |l|
                    l.gsub(/\t|\n|\r/, " ").squeeze(" ").strip
                end.select do |l|
                    l.length > 40 and l.length < 140 and l[0,1] == 
l[0,1].upcase and l[-3, 3] != "viz"
                end

                #Cache them for later
                File.open "ewd#{ewd}", "w" do |f|
                    YAML.dump lines, f
                end

                #Return a random sentence
                lines[rand lines.length]
            end
        end

        #(Very crudely) tries to find a quote that is related to the 
message.
        #Note that this only searches the cache.
        def find_related message

            #Get the longer words
            words = message.gsub(/[^a-zA-Z ]/, "").split.select { |w| 
w.length > 4 }

            quote = nil
            if words.length > 0
                word = /#{Regexp.union(words)}/i

                #Check local files for the search word
                Dir.glob("ewd*").find do |f|
                    text = File.read f
                    if text =~ word

                        #Get the list of quotes and grab one
                        quotes = YAML.load_file f
                        matches = quotes.grep word
                        quote = matches[rand(matches.length)]
                    else
                        false
                    end
                end
            else
                quote = get_quote
            end

            quote
        end
    end
end

#Post to Twitter
class DijkstraTwitter

    def initialize
        @twitter = Twitter::Client.new :login => "?", :password => "?"
    end

    #Post a random quote. If _ask_ is true, asks for approval first
    def post_random ask = false
        quote = DijkstraQuote.get_quote

        if ask and ask_permission("Would you like to post this: 
\"#{quote}\"") or not ask
            @twitter.status :post, quote
        end
    end

    #Check unanswered @ewd messages and come up with responses
    def post_replies ask = false
        require 'set'

        #Keep track of what has been replied to already
        if File.exist? "ewdreplies"
            replied = YAML.load_file "ewdreplies"
        else
            replied = Set.new
        end

        replies = @twitter.status(:replies)

        replies.each do |status|
            status_id = status.id.to_s
            next if replied.include? status_id
            message = status.text
            user = status.user.screen_name
            if reply_to message, user, ask
                replied << status_id
            end
        end

        File.open "ewdreplies", "w" do |f|
            YAML.dump replied, f
        end
    end

    #Send a reply if we can find one
    def reply_to message, sender, ask = false
        puts "Responding to \"#{message}\""

        quote = DijkstraQuote.find_related message
        if not quote
            puts "Nothing related. Skipping."
            return false
        elsif ask and ask_permission("Would you like to post this: 
\"#{quote}\"") or not ask
            response = "@#{sender} #{quote}"
            if response.length > 140
                response = response[0,140]
            end

            @twitter.status(:post, "@#{sender} #{quote}")
            true
        else
            if ask and ask_permission "Ignore this reply?"         
                true
            else
                false
            end
        end
    end

    #Ask if the user would like to post the quote which was found
    def ask_permission message
        puts message

        response = nil
        until response =~ /^(y|n)/i
            print "(Y/N): "
            response = gets
        end

        $1.downcase == "y"
    end
end

#Try it out
puts DijkstraQuote.get_quote

#dt = DijkstraTwitter.new
#dt.post_random true
#dt.post_replies true


My favorite so far: "In this sense, Programming = Mathematics + Murphy's 
Law"

-Justin