growlプラグインを自分好みに改造してみる

termtter本体添付のgrowlプラグインを、自分好みに改造してみました。

# -*- coding: utf-8 -*-

require 'tmpdir'
require 'open-uri'
require 'uri'
require 'fileutils'
require 'cgi'

begin
  require 'meow'
  growl = Meow.new('termtter', 'update_friends_timeline')
rescue LoadError
  growl = nil
end

config.plugins.growl.set_default(:icon_cache_dir, "#{Termtter::CONF_DIR}/tmp/user_profile_images")
config.plugins.growl.set_default(:growl_user, [])
config.plugins.growl.set_default(:growl_keyword, [])
FileUtils.mkdir_p(config.plugins.growl.icon_cache_dir) unless File.exist?(config.plugins.growl.icon_cache_dir)
unless File.exist?("#{config.plugins.growl.icon_cache_dir}/default.png")
  File.open("#{config.plugins.growl.icon_cache_dir}/default.png", "wb") do |f|
    f << open("http://static.twitter.com/images/default_profile_normal.png").read
  end
end

def get_icon_path(s)
  Dir.mkdir_p(config.plugins.growl.icon_cache_dir) unless File.exists?(config.plugins.growl.icon_cache_dir)

  /http:\/\/.+\/(\d+)\/.*?$/ =~ s.user.profile_image_url
  cache_file = "%s/%s-%s%s" % [  config.plugins.growl.icon_cache_dir,
                                 s.user.screen_name,
                                 $+,
                                 File.extname(s.user.profile_image_url)  ]
  unless File.exist?(cache_file)
    Thread.new(s,cache_file) do |s,cache_file|
      Dir.glob("#{config.plugins.growl.icon_cache_dir}/#{s.user.screen_name}-*") {|f| File.delete(f) }
      begin
        File.open(cache_file, "wb") do |f|
          f << open(URI.escape(s.user.profile_image_url)).read
        end
      rescue OpenURI::HTTPError
        cache_file = "#{config.plugins.growl.icon_cache_dir}/default.png"
      end
    end
  end
  return cache_file
end

def is_growl(s)
  return true if config.plugins.growl.growl_user.empty? && config.plugins.growl.growl_keyword.empty?
  if config.plugins.growl.growl_user.include?(s.user.screen_name) ||\
    Regexp.union(config.plugins.growl.growl_keyword) =~ s.text
    return true
  else
    return false
  end
end

Termtter::Client.register_hook(
  :name => :growl,
  :points => [:output],
  :exec_proc => lambda {|statuses, event|
    return unless event == :update_friends_timeline
    Thread.start do
      statuses.each do |s|
        next unless is_growl(s)
        growl_title = s.user.screen_name
        growl_title += " (#{s.user.name})" unless s.user.screen_name == s.user.name
        unless growl
          system 'growlnotify', growl_title, '-m', s.text.gsub("\n",''), '-n', 'termtter', '--image', get_icon_path(s)
        else
          begin
            icon = Meow.import_image(get_icon_path(s))
          rescue
            icon = Meow.import_image("#{config.plugins.growl.icon_cache_dir}/default.png")
          end
          growl.notify(growl_title, CGI.unescape(CGI.unescapeHTML(s.text)), :icon => icon) do
            s.text.gsub(URI.regexp) {|uri| system "open #{uri}"}
          end
        end
        sleep 0.1
      end
    end
  }
)

変更点

  • Meowでは「Growl通知をクリックしたときの動作」を設定できるので、「発言中にURLがあったら、それを開く」というアクションを設定してみました。
  • ユーザアイコンキャッシュのファイル名に、画像ファイルの親ディレクトリ名を含めるようにしました。
    • Twitterユーザアイコン画像の親ディレクトリは、数桁の数字になっていますが、これはアイコンを変更するごとに更新されるようです。この部分をみることで、アイコンの変更に追従しつつ、余計なアクセスをしなくてすむ、とおもいます。
#設定例
config.plugins.growl.growl_user = ['p2pquake', 'jihou']
config.plugins.growl.growl_keyword = ['地震', /^@User_Name/]
  • (screen nameとは別の)Nameが設定されていた場合、Growlのタイトルに表示するようにしました。
  • Twitterのデフォルトユーザアイコンをキャッシュしておいて、何らかの事情でアイコンが表示されない場合は、デフォルトのアイコンを表示するようにしました。

問題点

一部のユーザのアイコンをGrowlに渡すと、"CMSCreateDataProviderOrGetInfo : Invalid colorspace type"というエラーを吐きます(Meowでも、growlnotifyでも)。これは、「アイコン画像をGrowlで表示する際に、NSImageのインスタンスにしなければいけない」らしいのですが、その際に出てしまうエラーのようです。エラーが出ても、アイコン自体は表示されるのですが、エラーメッセージが標準出力に出力されてしまって、たいへんに不恰好です。