About

  • Macintosh の自作ソフトウェアを公開しています。
  • 配布しているアプリケーションは、すべてフリーウェアかつソースコードを公開しています。
  • コメント、不具合の報告、ご要望を待っています。

Donation

このサイトで配布しているソフトを常用されている方は寄付をお願いします。

2016.06.24

13:56:18Permanent Link

AppleScript ことはじめ」を書き直しました。

AppleScript で開発というより使い始めるための入門文書「AppleScript ことはじめ」をアップデートしました。

内容が、Mac OS X 10.5 Leopard 頃の古い内容だったので、最新の状況に合わせて全面アップデートしました。

画像も Retina ディスプレイを意識して、2倍の解像度で用意しています。

AppleScript の文法をいきなり覚えるのではなく、web で拾ってきたサンプルコードの活用ができるようになってくれれば良いな、と思っています。

2016.06.09

21:48:48Permanent Link

メール.app で選択されているで選択されているメッセージの添付ファイルを IMAP サーバーからダウンロードする(添付ファイルの文字化け回避)

OS X 付属のメール.app には昔から添付ファイルが文字化けするという持病がある。OS X 10.8 までは、MIMEfix という文字化けを修正してくれる秀逸なプラグインがあり、重宝していた。大変残念なことに開発が停止してしまい、OS X 10.9 以降に対応した MIMEfix プラグインが存在しない。

仕様がないから、iCloud の Web メールから添付ファイルをダウンロードしていた。大変めんどくさい。Web メールだと文字化けしないのだから、技術的に難しいことがあるわけでもない。単純に Apple の怠慢でバグが放置されていると思われる(しかし、そうとも言い切れない事情は後ほど)。

添付ファイルのファイル名は、MIME の Content-Disposition: フィールドの filename パラメータに RFC2231 もしくは MIME B でエンコーディングされて記述される。添付ファイルのファイル名が長いと、filename パラメータは複数行に分割されることがある。自分の調べた限りでは、filename フィールドが MIME B でエンコーディングされて、なおかつ複数行に分割されている場合は、ほとんど文字化けが起きる。しかし、そうでない場合もわずかにあるので悩ましい。自分が受け取る添付ファイルのファイル名は、ほとんどは MIME B でエンコードされているので文字化けしまくりである。

ちなみに、メール.app は添付ファイルのファイル名を RFC2331 でエンコードしている。そして、どんなに長いファイル名でも複数行に分割したりしない。Thunderbird もメール.appと同じように、RFC2331 でエンコードするとのこと。そして、メール.app と違ってファイル名が長いと複数行に分割するとのこと。その昔は、Thunderbird から複数行に分割されてエンコードされた添付ファイル名も文字化けしていたが、自分の試した限りでは OS X 10.11 のメール.app ではバグフィックスされている模様。

先ほど、文字化けするのは Apple の怠慢などと偉そうなことを言ったが、そもそも添付ファイル名は MEME B ではなく、RFC2331でエンコードしなければならない決まりになっている。MIME B でファイル名をエンコードして送りつけてくる 行儀の悪いWindows 系のメーラーが諸悪の根源である、というのが本当のところで、単純に Apple をせめてはかわいそうとも思う。

そうも言っていられないので、手軽に文字化けしていない添付ファイルを入手すべく、メール.app で選択されいるメッセージの添付ファイルを IMAP サーバーからダウンロードするスクリプトを書いた。もっぱら、ruby で書き始めたが、半分以上がヒアドキュメントで埋め込まれた AppleScript という不恰好なものになってしまった。

なお、実行には、gem で Mail ライブラリをインストールする必要があります。

#!/usr/bin/env ruby
# coding: utf-8

# Download attachments of selected message in Apple Mail.app
# form IMAP server.

require 'net/imap'
require 'pp'
require 'mail'
require 'yaml'
require 'pathname'
require 'optparse'

def main
  opts = ARGV.getopts('', 'raw_attachments', 'source')
  
  minfo = mail_info
  if minfo.nil? then
    exit 0
  end
  imap = Net::IMAP.new(minfo['server'], minfo['port'], minfo['use_ssl'])
  begin
    imap.login(minfo['user'], minfo['password'])
  rescue => e
    display_alert("Failed to login with error: #{e.message}")
    exit 0
  end
  imap.select(minfo['mailbox'])
  msg_ids = imap.search(["HEADER", "Message-Id", minfo['message_id']])
  a_msg = imap.fetch(msg_ids[0], "RFC822")
  if (opts['source']) then
    print a_msg[0].attr['RFC822']
  end
  m = Mail.new(a_msg[0].attr["RFC822"])
  if m.multipart? then
    saved_files = []
    m.attachments.each do |attachment|
      # 添付ファイルの種類とファイル名
      if opts['raw_attachments'] then
        print attachment
        next
      end
      
      # 添付ファイルの保存処理
      Dir.chdir(minfo['location'])
      filename = attachment.filename
      begin
        File.open(filename, "w+b") {|f|
          f.write attachment.body.decoded
          saved_files.push(Pathname.new(minfo['location'])+filename)
        }
      rescue => e
        #puts "添付ファイルの保存に失敗 #{e.message}"
        display_alert("Failed to save attachments with error: #{e.message}")
      end
    end
    if saved_files.length > 0 then
      final_message(saved_files)
    end
  end

  imap.disconnect
end

def mail_info
  mail_result = `osascript << EOS
tell application id "com.apple.Mail"
  set msgs to selection
  if (count msgs) < 1 then
    display alert "No selected messages."
    return ""
  end if
  
  set msgs_with_attachments to {}
  repeat with a_msg in msgs
    try
      set has_attachments to exists mail attachments of a_msg
    on error
      set has_attachments to true
    end try
    if has_attachments then
      set end of msgs_with_attachments to a_msg
    end if
  end repeat
end tell

set nmsg to count msgs_with_attachments
if nmsg < 1 then
   display alert "No attachements in the selected message."
   return ""
end if

if nmsg > 1 then
  set subject_list to {}
  tell application id "com.apple.Mail"
    set msg_idx to 1
    repeat with a_msg in msgs_with_attachments
      set dt to date received of a_msg
      set end of subject_list to (msg_idx as text) & space ¬
                           & (subject of a_msg) & tab ¬
                           & (short date string of dt) & space ¬
                           & (time string of dt)
      set msg_idx to msg_idx + 1
    end repeat
  end tell
  set a_result to choose from list subject_list with prompt "Choose a message to save attachments" without multiple selections allowed
  if class of a_result is not list then
    return ""
  end if
  set msg_idx to (word 1 of item 1 of a_result) as number
  set target_msg to item msg_idx of msgs_with_attachments
else
  set target_msg to first item of msgs_with_attachments
end if

tell application id "com.apple.Mail"
  tell target_msg
    try
      set has_attachments to exists mail attachments
    on error
      set has_attachments to true
    end try
    if not has_attachments then
      tell current application
        display alert "No attachements in the selected message."
      end tell
      return ""
    end if

    set msgid to message id
    set mbox to its mailbox
    set mbox_name to name of (its mailbox)
    set server_name to server name of account of (its mailbox)
    set use_ssl to uses ssl of account of (its mailbox)
    tell account of (its mailbox)
      set account_name to name
      set user_name to user name
      set port_number to port
    end tell
  end tell
  repeat
    set a_container to (get container of mbox)
    if (class of a_container) is not container then
      exit repeat
    end if
    set mbox to a_container
    set mbox_name to (name of mbox) & "/" & mbox_name
  end repeat
end tell

try
  set a_location to choose folder with prompt "Choose a location to save attachments"
on error
  return ""
end try
try
  set a_result to display dialog "Enter password for " & account_name default answer "" with hidden answer
on error
  return ""
end try
set a_password to text returned of a_result

set lf to ascii character 10
return "---" & lf & ¬
"server: " & server_name & lf & ¬
"port: " & port_number & lf & ¬
"use_ssl: " & (use_ssl as text) & lf & ¬
"user: " & user_name & lf & ¬
"password: " & a_password & lf & ¬
"mailbox: " & mbox_name & lf & ¬
"message_id: " & msgid & lf & ¬
"location: " & (POSIX path of a_location) & lf & ¬
"---"
EOS`
  if mail_result.length == 1 then
    return nil
  end
  #pp mail_result
  
  return YAML.load(mail_result) 
end

def display_alert(msg)
  `osascript << EOS
display alert "#{msg}"
EOS`
end

def final_message(saved_files)
  saved_files_joined = saved_files.join("\n")
  `osascript << EOS
set file_paths to "#{saved_files_joined}"
set a_result to display alert "Attachments are saved." message file_paths ¬
                   buttons {"Cancel", "Reveal", "OK"}
if button returned of a_result is "Reveal" then
  tell application id "com.apple.finder"
    reveal (paragraph 1 of file_paths as POSIX file)
    activate
  end tell
end if
EOS`
end

main

もう、Thunderbird に乗り換えようかな?

2016.05.29

23:48:04Permanent Link

ヨドバシ.com でノート PC の液晶保護フィルムを購入したら、梱包が衝撃的だった

ヨドバシ.com から大きなダンボール箱が届いた。

注文したのは液晶保護フィルムのはず。何か間違ったものが届いたのか、最近の液晶保護フィルムは激厚なのか。

中を開けてみると、ほとんど梱包材だった。なんという無駄。こんなものを注文してしまって、罪悪感を感じる

2016.05.28

01:08:13Permanent Link

Ruby の Nokogiri で読み込んだ XHTML を出力する

Ruby の Nokogiri は、HTML/XML の解析/生成/その他なんでもござれのすごいライブラリだ。Perl の HTML::Parser と HTML::TreeBuilder あわせたようなものと理解している。

先日、「Perl の HTML::TreeBuilder で元の HTML を出力する」という話をした。すなわち、HTML::TreeBuilder で HTML ファイルを読み込み、それを加工して出力するということの下準備というつもりです。入力したものをそのまま吐き出すことさえ、大変苦労した。おなじことを Ruby の Nokogiri ではどうなるだろうという話です。

結論から言えば、Nokogiri も読み込んだ XHTML を素直に吐き出してくれない。普通に to_xhtml メソッドで出力すると、Processing Instruction を Document Type Definition の後ろに出力してくれる。Processing Instruction はファイルの先頭にないと XSLT が適用されないので困る。

仕方がないので、次のスクリプトのように、Nokogiri::HTML のインスタンスから Processing Instruction を抜き出して自前で最初に出力するようにした。

#!/usr/bin/env ruby
# coding: utf-8

require 'rubygems'
require 'nokogiri'

doc =  Nokogiri::HTML <<-EOHTML
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>
<!-- comment1 -->
  <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <title>page title</title>
    <!-- comemnt2 -->
  </head>
  <body>
  <p>paragraph1</p>
  <ul>
  <li>item1</li>
  </ul>
  </body>
</html>
EOHTML
pi = doc.xpath('/processing-instruction()') 
puts pi.to_html # processing instruction を最初に出力
pi.remove
puts doc.to_xhtml(:encoding => 'UTF-8')

ストレートフォワードにはいかないけれど、HTML::TreeBuilder よりは圧倒的に素直に扱える。やっぱり、Ruby に浮気せざるえないか?

2016.05.27

20:43:03Permanent Link

TerminalCommander 3.0.1 : ターミナルでのシェルコマンドの実行支援

ターミナルでのシェルコマンドの実行を支援する AppleScript モジュールです。以下のような、複雑な処理を行いたいときにこのスクリプトはお役に立てるでしょう。

  • ターミナルウィンドウを追跡して、同じターミナルで何度もシェルコマンドを実行したい。
  • シェルコマンドの終了を待ってから、次の処理に移りたい。
  • シェルコマンドを実行するターミナルのシェルを指定したい。
  • ターミナルウィンドウのタイトルの設定、もしくは設定セットを指定したい。

< Previous Topics