[Radiant] Commentable and Spam

Guido Sohne guido at sohne.net
Wed Sep 6 12:37:33 CDT 2006


On 9/6/06, Sean Cribbs <seancribbs at gmail.com> wrote:
> One user, "Nolan", commented that CAPTCHA images are inaccessible (to
> vision-impaired users) and should not be considered an option.  As an
> alternative, he suggested the use of spam-filtering services like Akismet.
> While this seems a very promising option, that would be a whole lot of extra
> code (and perhaps another library, even a gem!) and probably outside the
> scope of my little behaviors.

If you write directly to the service by doing your own POST and parse
the results back, it should be doable in 200 lines or less. But you
would introduce depedencies on rexml and open-uri. I interfaced with
Flickr recently by doing the ff.

1) My model objects are based off

class Base

  def self.create(options)
    object = self.new
    options.each_pair { |k,v| object.method("#{k}=").call(v) }
    object
  end

end

To make them more ActiveRecord-like (am not using a database in this
particular app.

2) Call the REST api by constructing a URL, passing that to open-uri,
with a block that both selects what you want, and parses the response.

# build the URL
  def self.flickr(method, token, options,
rest="http://www.flickr.com/services/rest/?")
    raise FlickrError.new("hash expected") unless options.is_a?(Hash)
    raise FlickrError.new("url expected") unless rest && rest.is_a?(String)
    options.update('api_key' => @@api_id)
    options.update('method' => api(method)) if method
    options.update('auth_token' => token) if token
    signature = options.keys.sort.map { |k| "#{k}#{options[k]}" }.join('')
    parameters = options.keys.sort.map { |k|
"#{k}=#{url_escape(options[k])}" }.join('&')
    url = rest + parameters +
"&api_sig=#{Digest::MD5::hexdigest(@@shared_secret + signature)}"
    logger.debug "url: #{url}"
    url
  end

Now fetch and parse it

  def self.fetch(url, xpath, &block)
    open(url) do |reponse|
      xml = reponse.read
      logger.debug "xml: #{xml}"
      doc = REXML::Document.new xml
      doc.elements.each(xpath) { |element| yield element if block_given? }
    end
  end

And here's how I use it

  def self.find(token, method, options)
    photos = []
    fetch(flickr(method, token, options), 'rsp/photos/photo') do |element|
      photos << Photo.create(
        :id => element.attributes['id'],
        :secret => element.attributes['secret'],
        :title => element.attributes['title'],
        :server => element.attributes['server'],
        :ispublic => element.attributes['ispublic'] == '1',
        :isfriend => element.attributes['isfriend'] == '1',
        :isfamily => element.attributes['isfamily'] == '1',
        :owner => element.attributes['owner'],
        :longitude => element.attributes['longitude'].to_f,
        :latitude => element.attributes['latitude'].to_f,
        :geotagged => (element.attributes['longitude'].to_f != 0.0 ||
element.attributes['latitude'].to_f != 0.0)
      ) if element.attributes['id']
    end
    photos
  end

Simple and can be kept in only one extra file. If that file defines an
interface, then others can be written to handle different spam
services.

> Instead I'd like to get your thoughts on another method, although there is
> ample room for many methods down the road.  The method I'm considering is a
> challenge-response where the challenge is in human-readable text (a method
> suggested on some of the CAPTCHA pages I read).  I haven't worked out the
> particulars, but it would go something like "Subtract twenty-five from the
> sum of eighty and thirty-seven and enter the result as a number." The real
> ones would probably be easier than that, of course.  This could be
> automatically generated of course.  Another option is to say "Enter the
> subject of the sentence 'Jane walked to school.'" or something of that sort.

This would also be nice, though I prefer the automated, invisible
approach to one which gets in the way. Which one is more brittle is
also a concern. Is it the spammers write a reverser for your
generator, or akismet falls over under the load or they discover a big
hole in akismet's algorithms. I  think centralized spam detection will
do better but that's just a hunch.

-- G.



More information about the Radiant mailing list