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 dijkstra-twitter.rb · GitHub
#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