Archive for the ‘Ruby’ Category

Getting RSS from Apple mail to NetNewsWire

29 Comments »

I have been using Apple Mail to monitor my RSS feeds.  I like it because it’s right there when I check email.  Unfortunately, while I can set up my mail account using IMAP and have everything in sync regardless of whether I use the Mac Pro in the study, my Macbook Air, or my iPhone, the RSS feeds do not sync up.  So, sitting here in the hospital, I am unable to keep up with all the feeds on my main machine.  I did a bit of searching and found that NetNewsWire will do the trick of keeping them all synchronized (including the iPhone).

Downloaded…Installed…now, just import my RSS feeds from Apple Mail…hmmmmm…. search Google… only hit is this blog entry bemoaning the lack of this capability.

Well now, a bored programmer, laid up in the hospital, feeling just fine, and sharing the same need can’t just let a situation like this stand.

So, here’s a ruby script that will read the RSS feeds from your Apple Mail and create an opml file named AppleMailRSS.opml on your desktop.  This file will import into NetNewsWire and, though I’ve not tested anything else, presumably any other RSS reader that imports opml files.

(Update note: (March 21, 2009) I’ve updated the script to handle any RSS feeds in folders in Apple Mail. Thanks Sebastion for pointing this out. I was not using folders in Apple Mail so I missed this detail.)

require 'rexml/document'

skeleton = <<EOSTRING
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.1">
  <head>
    <title>Subscriptions from Apple Mail</title>
    </head>
  <body>
  </body>
</opml>
EOSTRING

def getFolder(node,folders)
  return node if folders.length == 0
  folder = folders.shift
  node.each_element("outline[@title='#{folder}']") do |fNode|
    return getFolder(fNode,folders)
  end
  fNode = REXML::Element.new 'outline'
  fNode.attributes['text'] = folder
  fNode.attributes['title'] = folder
  node << fNode
  return getFolder(fNode,folders)
end

def addRSSNode(body,folders,title,url)
  ol = REXML::Element.new 'outline'
  ol.attributes['text'] = title
  ol.attributes['description'] = ''
  ol.attributes['title'] = title
  ol.attributes['type'] = 'rss'
  ol.attributes['version'] = 'RSS'
  ol.attributes['htmlUrl'] = ''
  ol.attributes['xmlUrl'] = url
  getFolder(body,folders) << ol
end

doc = REXML::Document.new(skeleton)
body = REXML::XPath.match(doc,'//body')[0]
Dir.chdir()
Dir.chdir('Library/Mail/RSS')
Dir.glob('**/*.rssmbox') do |mbox|
  m = mbox.match(/(.*\/)*(.*).rssmbox/)
  folders = m[1] ? m[1].split('/') : []
  title = m[2]
  File.open("#{mbox}/Info.plist") do |file|
    url = file.read.match(/RSSFeedURLString.*?<string>(.*?)</m)[1]
    addRSSNode(body,folders,title,url)
  end
end

Dir.chdir()
File.open('Desktop/AppleMailRSS.opml','w') do |file|
  doc.write( file, 0 )
end

IMAP Purge revisited

No Comments »

I noticed, looking at the logs this morning, that the previous script bombs if you delete a mail folder without removing it from your yaml configuration. So, here’s a new and (slightly) improved version

Along with handling the exception, it now creates and appends to a log file per month (instead of creating one each day which proved a bit “messy”).

#!/usr/bin/ruby
require 'net/imap'
require 'date'
require 'yaml'

Config = Struct.new(:imapServer, :userid, :password, :purgePeriods)

root = File.dirname($0) +File::SEPARATOR
t = Date.today
$stderr = $stdout = File.new(root+"cleanup_#{t.year}_#{t.month}.log",'a')
config = YAML.load_file(root+'mailClean.yaml')

imap = Net::IMAP.new(config.imapServer)
imap.login(config.userid,config.password)

puts "================n================"
puts "executing : #{Time.now}"
puts "================n================"
config.purgePeriods.each do |mailbox,days|
  pDate = Date.today - days
  delCount = 0
  puts "====(#{mailbox}:purge before #{pDate})===="
  begin
    imap.select(mailbox)
    msns = imap.search(['NOT','NEW'],'US-ASCII')
    threads = {}
    sl = msns.slice!(0..200)
    while (sl.length>1)
      sl = [sl] if sl.class == 'Fixnum'
      data = imap.fetch(sl,['ENVELOPE'])
      data.each do |d|
        env = d.attr['ENVELOPE']
        next unless env.date
        subject =  env.subject || ''
        threadSubject = subject.sub(/^Re:s*/i,'')
        dt = Date.parse(env.date)
        if threads[threadSubject]
          t = threads[threadSubject]
          t[:latest] = dt if dt > t[:latest]
          t[:msns] << d.seqno
        else
          threads[threadSubject] = {:latest => dt,:msns =>[d.seqno]}
        end
      end
      sl = msns.slice!(0..200)
    end
    threads.each do |key,val|
      if val[:latest] < pDate
        puts "#{key} == #{val[:latest]} #{val[:msns].inspect}n"
        delCount += val[:msns].length
        imap.store(val[:msns],'+FLAGS',[:Deleted])
      end
    end
    puts "deleted #{delCount} messagesn"
    imap.expunge
  rescue
    puts "Error Encountered!:#{$!}"
  end
end

imap.logout

Thread aware IMAP message purge

No Comments »

I subscribe to a number of mailing lists, and use .mac (mobile.me) IMAP for my email. I don’t like to delete everything immediately (so I can follow back through a thread if needed). So, I started looking for a way with mac mail, to have message threads cleaned up after a period of time. Of course, I’d like that period of time to vary by mailbox.

Naturally, this doesn’t seem to be possible with the native capabilities of mac mail, so I started looking into scripting something for mac mail. After a bit of looking around, this seemed less than productive, and it dawned on me that Ruby has an IMAP library. So I set about building the following script to take care of keeping my mailboxes managed.

The idea is that any message in a thread with a message in the last X days (configurable by mailbox) will be retained. All other messages are deleted.

The code is not particularly optimized, and may take some time, particularly on the initial execution if you have a lot of history built up. I’m open to suggestions for improvement, so fire away.

WARNING: This code is tested with the mobile.me IMAP server for my personal needs. If you want to use it, please feel free, but you may have to modify it for your particular IMAP server. I make no warranty, implied, inferred, suggested, specific, or otherwise that it will suit your purposes, that it will not delete every last email you’ve ever received, etc. (but hey, it works for me, and it might save you some time in a similar task)

“cleanMail.rb”:

#!/usr/bin/ruby
require 'net/imap'
require 'date'
require 'time'
require 'yaml'

Config = Struct.new(:imapServer, :userid, :password, :purgePeriods)    

root = File.dirname($0) +File::SEPARATOR
$stderr = $stdout = File.new(root+"cleanup_#{Date.today}.log",'a')
config = YAML.load_file(root+'mailClean.yaml')

imap = Net::IMAP.new(config.imapServer)
imap.login(config.userid,config.password)

puts "executing : #{Time.now}"
config.purgePeriods.each do |mailbox,days|
  pDate = Date.today - days
  delCount = 0
  puts "====(#{mailbox}:purge before #{pDate})===="
  imap.select(mailbox)
  msns = imap.search(['NOT','NEW'],'US-ASCII')
  threads = {}
  sl = msns.slice!(0..200)
  while (sl.length>1)
    sl = [sl] if sl.class == 'Fixnum'
    data = imap.fetch(sl,['ENVELOPE'])
    data.each do |d|
      env = d.attr['ENVELOPE']
      next unless env.date
      subject =  env.subject || ''
      threadSubject = subject.sub(/^Re:s*/i,'')
      dt = Date.parse(env.date)
      if threads[threadSubject]
        t = threads[threadSubject]
        t[:latest] = dt if dt > t[:latest]
        t[:msns] << d.seqno
      else
        threads[threadSubject] = {:latest => dt,:msns =>[d.seqno]}
      end
    end
    sl = msns.slice!(0..200)
  end
  threads.each do |key,val|
    if val[:latest] < pDate
      puts "#{key} == #{val[:latest]} #{val[:msns].inspect}n"
      delCount += val[:msns].length
      imap.store(val[:msns],'+FLAGS',[:Deleted])
    end
  end
  puts "deleted #{delCount} messagesn"
  imap.expunge
end

imap.logout

You’ll need to have a “mailClean.yaml” file similar to the following in the same directory as the cleanMail.rb file. Of course, you’ll need to supply your own IMAP server name, user id, and password. And, the example mailboxes are mine, so you’ll need to tailor to your mailbox structure (just leave out mailboxes that you don’t want the script to manage). in the purgePeriods, just enter each IMAP mailbox you want to manage and the number of days after which a thread should should expire.

--- !ruby/struct:Config
imapServer: <your imap server>
password: <your password>
userid: <your userid>
purgePeriods:
  Tech: 30
  Mac/iTunes: 30
  Humor/Jokeroo: 360
  Drafts: 360
  Deals: 30
  Junk: 30
  Programming/Ruby/Heroku: 60
  Mac/MacWorld: 90
  CNET: 30
  Programming/Ruby/Sripting Leopard: 90
  Programming/Ruby/IronRuby: 90
  Programming/ANTLR: 60
  Humor/Clean Laffs: 30
  Humor/Bizarre Video: 30
  Humans: 30
  Programming/Ruby/Ruby on Rails: 90
  Programming/GWT: 30
  Mac/Apple: 360
  Lockergnome: 30
  Programming/Ruby/Rb Appscript: 30
  Troop 169: 120
  Programming/Ruby/Ruby Quiz: 30
  Programming/Ruby/Ruby Core: 90


Edit translation
Machine translation (Google):
Loading...
Copy to editor
or Cancel