growlプラグインで、priorityとstickyを設定できるようにしてみた
うれしいことに、先日書いたGrowlプラグインがtermtter本体に取り込まれることとなりました。blogは書いてみるものですね。
せっかくなので、Meowの機能をひととおり使ってみようと、priorityとstickyの設定をできるようにしてみました。
# -*- coding: utf-8 -*- 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, []) config.plugins.growl.set_default(:priority_veryhigh_user, []) config.plugins.growl.set_default(:priority_high_user, []) config.plugins.growl.set_default(:priority_normal_user, []) config.plugins.growl.set_default(:priority_low_user, []) config.plugins.growl.set_default(:priority_verylow_user, []) config.plugins.growl.set_default(:priority_veryhigh_keyword, []) config.plugins.growl.set_default(:priority_high_keyword, []) config.plugins.growl.set_default(:priority_normal_keyword, []) config.plugins.growl.set_default(:priority_low_keyword, []) config.plugins.growl.set_default(:priority_verylow_keyword, []) config.plugins.growl.set_default(:sticky_user, []) config.plugins.growl.set_default(:sticky_keyword, []) growl_keys = { 'user' => config.plugins.growl.growl_user, 'keyword' => Regexp.union(config.plugins.growl.growl_keyword) } priority_keys = { 'user' => [config.plugins.growl.priority_veryhigh_user, config.plugins.growl.priority_high_user, config.plugins.growl.priority_normal_user, config.plugins.growl.priority_low_user, config.plugins.growl.priority_verylow_user], 'keyword' => [Regexp.union(config.plugins.growl.priority_veryhigh_keyword), Regexp.union(config.plugins.growl.priority_high_keyword), Regexp.union(config.plugins.growl.priority_normal_keyword), Regexp.union(config.plugins.growl.priority_low_keyword), Regexp.union(config.plugins.growl.priority_verylow_keyword) ] } sticky_keys = { 'user' => config.plugins.growl.sticky_user, 'keyword' => Regexp.union(config.plugins.growl.sticky_keyword) } FileUtils.mkdir_p(config.plugins.growl.icon_cache_dir) unless File.exist?(config.plugins.growl.icon_cache_dir) Dir.glob("#{config.plugins.growl.icon_cache_dir}/*") {|f| File.delete(f) unless File.size?(f) } 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) /https?:\/\/.+\/(\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 s.user.profile_image_url.sub!(/^https/,'http') 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 get_priority(s,priority_keys) priority = 2 5.times {|n| return priority.to_s if priority_keys['user'][n].include?(s.user.screen_name) ||\ priority_keys['keyword'][n] =~ s.text priority -= 1 } return '0' end def is_growl(s,growl_keys) return true if (growl_keys['user'].empty? && growl_keys['keyword'] == /(?!)/) ||\ (growl_keys['user'].include?(s.user.screen_name) || growl_keys['keyword'] =~ s.text) return false end def is_sticky(s,sticky_keys) return true if sticky_keys['user'].include?(s.user.screen_name) || sticky_keys['keyword'] =~ s.text return false 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_keys) growl_title = s.user.screen_name growl_title += " (#{s.user.name})" unless s.user.screen_name == s.user.name unless growl arg = ['growlnotify', growl_title, '-m', s.text.gsub("\n",''), '-n', 'termtter', '-p', get_priority(s,priority_keys), '--image', get_icon_path(s)] arg.push('-s') if is_sticky(s,sticky_keys) system *arg 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, :priority => get_priority(s,priority_keys), :sticky => is_sticky(s,sticky_keys) }) do s.text.gsub(URI.regexp) {|uri| system "open #{uri}"} end end sleep 0.1 end end } ) #Optional setting example. # Growl ON setting. # config.plugins.growl.growl_user = ['p2pquake', 'jihou'] # config.plugins.growl.growl_keyword = ['地震', /^@screen_name/] # Priority setting. # config.plugins.growl.priority_veryhigh_user = ['veryhigh_user'] # config.plugins.growl.priority_veryhigh_keyword = ['veryhigh_keyword', /^@screen_name/] # config.plugins.growl.priority_high_user = ['high_user'] # config.plugins.growl.priority_high_keyword = ['high_keyword'] # config.plugins.growl.priority_low_user = ['low_user'] # config.plugins.growl.priority_low_keyword = ['low_keyword'] # config.plugins.growl.priority_verylow_user = ['verylow_user'] # config.plugins.growl.priority_verylow_keyword = ['verylow_keyword'] # Sticky setting. # config.plugins.growl.sticky_user = ['screen_name'] # config.plugins.growl.sticky_keyword = [/^@screen_name/, '#termtter']
priorityの設定については、以下のように記述します。重要度ごとにユーザ名、キーワード(正規表現可)を設定してください。重要度の高い設定ほど優先的に適用されます。
たとえば「やや重要」に設定したユーザが「緊急」に設定したキーワードを投稿したら、priorityは「緊急」になります。
逆に、「緊急」に設定したユーザが「やや重要」のキーワードを投稿した場合も、priorityは「緊急」になります。
#緊急 config.plugins.growl.priority_veryhigh_user = ['veryhigh_user'] config.plugins.growl.priority_veryhigh_keyword = ['veryhigh_keyword', /^@screen_name/] #重要 config.plugins.growl.priority_high_user = ['high_user'] config.plugins.growl.priority_high_keyword = ['high_keyword'] #やや重要 config.plugins.growl.priority_low_user = ['low_user'] config.plugins.growl.priority_low_keyword = ['low_keyword'] #重要でない config.plugins.growl.priority_verylow_user = ['verylow_user'] config.plugins.growl.priority_verylow_keyword = ['verylow_keyword']
stickyを設定するユーザおよびキーワードは、以下のような感じで。
config.plugins.growl.sticky_user = ['screen_name1', 'screen_name2'] config.plugins.growl.sticky_keyword = [/^@screen_name/, '#termtter']
設定に関しては、もうすこし簡潔な方法がありそうなのですが、おもいつきませんでした。まだまだ勉強不足です。
Post時に、URLをbit.lyで短縮するプラグインを書いてみた
# -*- coding: utf-8 -*- require 'uri' require 'open-uri' config.plugins.bitly.set_default(:length_to_shorten, '40') length_to_shorten = config.plugins.bitly.length_to_shorten.to_i login = config.plugins.bitly.login key = config.plugins.bitly.key if login.empty? || key.empty? puts 'Need your "bit.ly login name" & "API Key"' puts 'please set config.plugins.bitly.login & config.plugins.bitly.key' puts 'your API Key is here => http://bit.ly/account/' else Termtter::Client.register_hook( :name => :bitly, :points => [:modify_arg_for_update], :exec_proc => lambda {|cmd, arg| long_url = [] arg.gsub(URI.regexp) {|uri| long_url.push(uri) unless uri.size < length_to_shorten || /^http:\/\/bit\.ly\// =~ uri } return arg if long_url.empty? api_query = "http://api.bit.ly/shorten?version=2.0.1&login=#{login}&apiKey=#{key}" long_url.each {|url| api_query += "&longUrl=#{url}"} begin response_json = open(api_query).read rescue OpenURI::HTTPError puts "bit.ly access error : #{$!}" return arg end if /"statusCode": "OK"/ =~ response_json long_url.each {|url| /#{Regexp.escape(url)}.*?"shortUrl": "(.*?)"/m =~ response_json arg.sub!(url, $1) } else /"errorMessage": "(.*?)"/ =~ response_json puts "bit.ly API error : #{$1}" end arg } ) end #Necessary settings. # config.plugins.bitly.login = 'YOUR LOGIN NAME' # config.plugins.bitly.key = 'API KEY' #Optional setting. # config.plugins.bitly.length_to_shorten = '40' # #Get API Key at http://bit.ly/account/
bit.lyのAPIを利用するには、アカウントを登録してAPI Keyの取得が必要です。そのうえで、アカウントのログインネームとAPI keyをそれぞれ"config.plugins.bitly.login"と"config.plugins.bitly.key"に設定してください。
また、"config.plugins.bitly.length_to_shorten"に設定した文字数以上のURLのみを短縮します(デフォルト値の'40'は、Twitterの仕様に倣ってみました)。
ついでに、URLをtweetburnerで短縮するプラグインも書いてみた
# -*- coding: utf-8 -*- require 'net/http' config.plugins.tweetburner.set_default(:length_to_shorten, '40') config.plugins.tweetburner.set_default(:open_timeout, '4') config.plugins.tweetburner.set_default(:read_timeout, '6') length_to_shorten = config.plugins.tweetburner.length_to_shorten.to_i open_timeout = config.plugins.tweetburner.open_timeout.to_i read_timeout = config.plugins.tweetburner.read_timeout.to_i Termtter::Client.register_hook( :name => :tweetburner, :points => [:modify_arg_for_update], :exec_proc => lambda {|cmd, arg| long_url = [] arg.gsub(URI.regexp) {|uri| long_url.push(uri) unless uri.size < length_to_shorten || /^http:\/\/twurl\.nl\// =~ uri } return arg if long_url.empty? Net::HTTP.version_1_2 http = Net::HTTP.new('tweetburner.com') http.open_timeout = open_timeout http.read_timeout = read_timeout begin http.start do long_url.each {|longurl| response = http.post('/links', "link[url]=#{longurl}") if response.code == '200' arg.sub!(longurl, response.body) else puts "Tweetburner error : #{response.code}" end } end rescue Timeout::Error puts 'Tweetburner access timeout' end arg } ) #Optional setting. # config.plugins.tweetburner.length_to_shorten = '40'
もののついでに、tweetburnerでURLを短縮するプラグインも書いてみました。
せっかくなので、見よう見まねでタイムアウトの処理も書いてみましたが、タイムアウト時間などが設定できても、あまりうれしくないですね。
「bit.lyに登録するのは面倒だけど、ちょっと統計情報があったらうれしい」という時につかえるかと思ったのですが、総じてAPIを使うよりWeb上から利用したほうが便利なサービスのようです。
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 } )
変更点
- Growl通知のライブラリに"Meow"を使ってみました(参考:RubyからGrowlに通知するためのライブラリ、Meowをちょっと使ってみたので自分メモ - griffin-stewieの日記)
- Meowでは「Growl通知をクリックしたときの動作」を設定できるので、「発言中にURLがあったら、それを開く」というアクションを設定してみました。
- ユーザアイコンキャッシュのファイル名に、画像ファイルの親ディレクトリ名を含めるようにしました。
- 「termtterのgrowlプラグインを改造してみた - みこまやのひび」を参考に、特定のユーザ、キーワードのみGrowl通知をする、という設定を追加してみました。デフォルトのままであれば、すべてを通知します。
#設定例 config.plugins.growl.growl_user = ['p2pquake', 'jihou'] config.plugins.growl.growl_keyword = ['地震', /^@User_Name/]
- (screen nameとは別の)Nameが設定されていた場合、Growlのタイトルに表示するようにしました。
- Twitterのデフォルトユーザアイコンをキャッシュしておいて、何らかの事情でアイコンが表示されない場合は、デフォルトのアイコンを表示するようにしました。
termtterのプラグインをいじってみる
プログラミングに関するブログを読んだり、Twitterでプログラマな人をfollowしたりしていると、「ネットユーザーはおしなべてプログラミングができるもんだ」という錯覚におちいります。
プログラミングができるのは素敵だなあ、ということで、ぼくも最近Rubyをおぼえはじめました。はじめてみれば、これがなかなかおもしろいのですね。
さしあたり、てごろな教材としてtermtterのプラグインをいじっていきたいとおもいます。
てはじめに、confirm.rbに手を加えてみました(というほどのものでもないですが)。
# -*- coding: utf-8 -*- Termtter::Client.register_hook( :name => :confirm, :points => [:pre_exec_update], :exec_proc => lambda {|cmd, arg| if /^y?$/i !~ Readline.readline("update? #{arg} (#{arg.split(//).size}) [Y/n] ", false) puts 'canceled.' raise Termtter::CommandCanceled end } )
termtterは、他のクライアントにあるような「入力文字数の表示」がなさそう(Ver 1.0.7 現在)だったので、confirmの際にそれができたらうれしいな、ということでconfirmのプロンプトに"(文字数)"という形で表示されるようにしてみました。
こういう、ちょとしたことを簡単にできるのは、すばらしいですね。
自作や独自に改造したプラグインは、"~/.termtter/plugins"というディレクトリを作って、その中にいれておけば、termtter本体に添付されているプラグインより優先して読み込まれるようです。
outputzプラグインのエラー
termtterのoutputzプラグインがときおりエラーを吐くのですが、outputzにポストする前に変数argが空になるのが原因(Thread - Rubyリファレンスマニュアル)ではないかとおもったので、以下のようにしてみました。
とりあえず、エラーは吐かなくはなったようですが、これで大丈夫なのでしょうか。
# -*- coding: utf-8 -*- module Termtter::Client config.plugins.outputz.set_default(:uri, 'termtter://twitter.com/status/update') key = config.plugins.outputz.secret_key if key.empty? puts 'Need your secret key' puts 'please set config.plugins.outputz.secret_key' else register_hook( :name => :outputz, :points => [:pre_exec_update], :exec_proc => lambda {|cmd, arg| Thread.new(arg) do |arg| Termtter::API.connection.start('outputz.com', 80) do |http| key = CGI.escape key uri = CGI.escape config.plugins.outputz.uri size = arg.split(//).size http.post('/api/post', "key=#{key}&uri=#{uri}&size=#{size}") end end } ) end end # outputz.rb # a plugin that report to outputz your post # # settings (note: must this order) # config.plugins.outputz.secret_key = 'your secret key' # plugin 'outputz'