Archive for the 'NSMutableDictionary' Category

2017/10/18 QuartzComposerでグラフ表示てすと v4(10.13対応)

QuartzComposerのCompositionを任意のパラメータでレンダリングして表示するAppleScriptのmacOS 10.13.x対応版です。

composition.png

macOS 10.13で変更になったのはQuartzComposerまわりではなく、NSDictionary/NSMutableDictionaryまわりで、キーと値を列挙して、

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aDict to (current application’s NSMutableDictionary’s dictionaryWithObjectsAndKeys_(1, “aKey”, 2, “bKey”, missing value)) as record
–>  {bKey:2, aKey:1}

★Click Here to Open This Script 

などとNSDictionary/NSMutableDictionaryを作成するタイプのメソッド(末尾にmissing valueがつくもの)です。macOS 10.13上で実行すると、

1013error.jpg

このように(↑)エラーになるので、

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aDict to (current application’s NSDictionary’s dictionaryWithObjects:{1, 2} forKeys:{“aKey”, “bKey”}) as record
–> {bKey:2, aKey:1}

★Click Here to Open This Script 

などと書き換える必要があります(Thanks Shane!)。

前者の書き方については、書くのが面倒に感じていたので本Blog中でもかぞえるほどしか登場していませんでした。10.13向けの書き換え例としてこれが向いていると判断して掲載してみた次第です。QuartzComposer自体にはとくに思い入れもありません。

なお、表示対象のQuartzCompositionは、本Script Bundle中に入っていることを前提としていますが、バンドル外のものを表示するように書き換えるのも簡単なので実験してみるとよいでしょう。

comp_loc.png

本Script自体はmacOS 10.12上で作成して実行確認しています(当然、10.13.1beta上でも動作確認していますが)。10.13以降で追加になった機能を前提としていません。

–> Download script bundle including Composition

AppleScript名:QuartzComoserでグラフ表示てすと v4(10.13対応)
– Created 2015-11-03 by Takaaki Naganoya
– Modified 2017-10-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
use framework “Carbon” – AEInteractWithUser() is in Carbon
–http://piyocast.com/as/archives/4900

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSWindowCloseButton : a reference to current application’s NSWindowCloseButton
property NSScreen : a reference to current application’s NSScreen
property NSPredicate : a reference to current application’s NSPredicate
property NSDictionary : a reference to current application’s NSDictionary
property NSBackingStoreBuffered : a reference to current application’s NSBackingStoreBuffered
property NSMutableArray : a reference to current application’s NSMutableArray
property NSTitledWindowMask : a reference to current application’s NSTitledWindowMask
property NSString : a reference to current application’s NSString
property NSWindow : a reference to current application’s NSWindow
property NSNumber : a reference to current application’s NSNumber
property NSNormalWindowLevel : a reference to current application’s NSNormalWindowLevel
property QCView : a reference to current application’s QCView
property NSColor : a reference to current application’s NSColor

if current application’s AEInteractWithUser(-1, missing value, missing value) is not equal to 0 then return

set chartData to NSMutableArray’s new()

–chartData’s addObject:(NSMutableDictionary’s dictionaryWithObjectsAndKeys_(”練馬区”, “label”, 3, “value”, missing value))–older way (Obsolete in 10.13)
chartData’s addObject:(my recWithLabels:{“label”, “value”} andValues:{“練馬区”, 3})
chartData’s addObject:(my recWithLabels:{“label”, “value”} andValues:{“青梅市”, 1})
chartData’s addObject:(my recWithLabels:{“label”, “value”} andValues:{“中野区”, 2})

–上記データの最大値を求める
set aMaxRec to chartData’s filteredArrayUsingPredicate:(NSPredicate’s predicateWithFormat_(“SELF.value == %@.@max.value”, chartData))
set aMax to value of aMaxRec
set aMaxVal to (first item of aMax) as integer

–Scalingの最大値を求める
if aMaxVal 10 then
  set aScaleMax to (10 div aMaxVal)
  
set aScaleMin to aScaleMax div 10
else
  set aScaleMax to (10 / aMaxVal)
  
set aScaleMin to 1
end if

try
  set aPath to path to resource “Chart.qtz”
on error
  return
end try

set qtPath to NSString’s stringWithString:(POSIX path of aPath)

set aView to QCView’s alloc()’s init()
set qtRes to (aView’s loadCompositionFromFile:qtPath)

aView’s setValue:chartData forInputKey:“Data”
aView’s setValue:(NSNumber’s numberWithFloat:(0.5)) forInputKey:“Scale”
aView’s setValue:(NSNumber’s numberWithFloat:(0.2)) forInputKey:“Spacing”
aView’s setAutostartsRendering:true

set maXFrameRate to aView’s maxRenderingFrameRate()

set aWin to (my makeWinWithView(aView, 800, 600, “AppleScript Composition Test”))

(aView’s setValue:(NSNumber’s numberWithFloat:aScaleMax / 10) forInputKey:“Scale”)
delay 5

my closeWin:aWin
aView’s stopRendering() –レンダリング停止

–make Window for Display
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle)
  set aScreen to NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
  
set aBacking to NSTitledWindowMask
  
  
set aDefer to NSBackingStoreBuffered
  
  
– Window
  
set aWin to NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
aWin’s setBackgroundColor:(NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
aWin’s makeKeyAndOrderFront:(me)
  
–aWin’s movableByWindowBackground:true
  
  
– Set Custom View
  
aWin’s setContentView:aView
  
  
–Set Close Button  
  
set closeButton to NSWindow’s standardWindowButton:(NSWindowCloseButton) forStyleMask:(NSTitledWindowMask)
  
  
return aWin
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

on recWithLabels:theKeys andValues:theValues
  return (NSDictionary’s dictionaryWithObjects:theValues forKeys:theKeys) as record
end recWithLabels:andValues:

★Click Here to Open This Script 

2017/10/10 なろう小説APIで各カテゴリごとの集計を実行

「小説家になろう」サイトのAPI「なろう小説API」を呼び出して、カテゴリごとの該当件数を集計するAppleScriptです。

「なろう小説API」は事前にAPI Keyの取得も不要で、簡単に呼び出せるのでお手軽に使えます。

本AppleScriptは、「小説家になろう」掲載作品を、大カテゴリと小カテゴリでコードを指定して、ループで存在件数の集計を行います。カテゴリごとに分布が偏っているようなので、該当件数が0件のカテゴリは結果出力しないようにしています。筆者の環境では集計に22〜25秒ぐらいかかっています(インターネット接続回線速度に依存)。

http headerにgzip転送リクエスト要求を書きつつ、実際のデータ自体もgzipで圧縮されているので、二重に圧縮している状態です。実測したところ、http headerでgzip指定を行なったほうがトータルで1秒程度速かったので「そんなもんかな」と思いつつ、そのままにしています。

Web APIからのデータ受信時のNSDataからのZip展開にオープンソースのフレームワーク「GZIP」(By Nick Lockwood)を利用しています。同プロジェクトはGithub上のXcodeプロジェクトをXcodeでビルドするとFrameworkが得られるので、ビルドして~/Library/FrameworksフォルダにGZIP.frameworkを入れてください。

ジャンルは数値で指定するようになっていますが、その数値が何を示しているかという情報はAPI側からの出力にはないので、Webサイト上から文字情報をコピペで取得し、AppleScript内に記載して(ハードコーディング)カテゴリコードリストと照合して出力しています。

実際に集計してみると、ノンカテゴリが53%ということで、カテゴリ分けの機能が有効に活用されていないことが見てとれます。

そのことについては運営側も重々承知しているようで、APIの検索オプションに「キーワードに異世界転生があるものを含む」といったものがあるなど、ジャンルよりもキーワード重視するようにしているようです。

そういいつつも、使われているキーワードについては若干の表記ゆらぎがあるようで、単純にこのオプションを指定しても「異世界転生もの」をすべて抽出できていないように見えます。キーワード自体にどの程度「表記揺れ」が存在しているのかを調べてみるとよいのかもしれません。

APIの仕様上、2,000件しか詳細データを取得できないように見えるので、そのあたりがちょっと気になります(どうも全数調査を行いにくい仕様)。

分析するまでもなく、異世界転生ものが多く、ノンジャンル作品でも異世界転生ものばっかりという印象です。掘り出しもので「ソ連の宇宙技術は最強過ぎたのだが、それを西側諸国が完全に理解したのはつい最近だった」という作品に行き当たり、これが強烈に面白いです。

AppleScript名:なろう小説APIで各カテゴリごとの集計を実行
– Created 2017-10-10 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4891
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “GZIP”
–https://github.com/nicklockwood/GZIP
–http://dev.syosetu.com/man/api/
–1日の利用上限は80,000または転送量上限400MByte???

property |NSURL| : a reference to current application’s |NSURL|
property NSString : a reference to current application’s NSString
property NSArray : a reference to current application’s NSArray
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property NSURLConnection : a reference to current application’s NSURLConnection
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSNumber : a reference to current application’s NSNumber
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSNumberFormatterRoundUp : a reference to current application’s NSNumberFormatterRoundUp
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set invList to {}

set bgList to {1, 2, 3, 4, 99, 98}
set bigGnereLabel to {“恋愛”, “ファンタジー”, “文芸”, “SF”, “その他”, “ノンジャンル”}

set gList to {101, 102, 201, 202, 301, 302, 303, 304, 305, 306, 307, 401, 402, 403, 404, 9901, 9902, 9903, 9904, 9999, 9801}
set smlGenreLabel to {“異世界〔恋愛〕”, “現実世界〔恋愛〕”, “ハイファンタジー〔ファンタジー〕”, “ローファンタジー〔ファンタジー〕”, “純文学〔文芸〕”, “ヒューマンドラマ〔文芸〕”, “歴史〔文芸〕”, “推理〔文芸〕”, “ホラー〔文芸〕”, “アクション〔文芸〕”, “コメディー〔文芸〕”, “VRゲーム〔SF〕”, “宇宙〔SF〕”, “空想科学〔SF〕”, “パニック〔SF〕”, “童話〔その他〕”, “詩〔その他〕”, “エッセイ〔その他〕”, “リプレイ〔その他〕”, “その他〔その他〕”, “ノンジャンル〔ノンジャンル〕”}

–全体の件数取得
set aRec to {gzip:“5″, out:“json”, lim:“1″}
set aRESTres to callNarouAPI(aRec, “1″, “1″) of me
set wholeCount to (allCount of first item of aRESTres)

–カテゴリごとの集計
repeat with i in bgList
  repeat with ii in gList
    set aRec to {gzip:“5″, biggenre:i as string, genre:ii as string, out:“json”, lim:“1″}
    
set aRESTres to callNarouAPI(aRec, “1″, “1″) of me
    
set aTotal to allCount of first item of aRESTres
    
    
if aTotal is not equal to 0 then
      set big to contents of i
      
set small to contents of ii
      
set bigLabel to getLabelFromNum(bgList, bigGnereLabel, big) of me
      
set smlLabel to getLabelFromNum(gList, smlGenreLabel, small) of me
      
set aPerCentatge to roundingDownNumStr(((aTotal / wholeCount) * 100), 1) of me
      
set the end of invList to {biggenre:bigLabel, genre:smlLabel, totalNum:aTotal, percentage:aPerCentatge}
    end if
  end repeat
end repeat

set bList to sortRecListByLabel(invList, “totalNum”, false) of me –降順ソート
–> {{totalNum:274072, biggenre:”ノンジャンル”, percentage:53.1, genre:”ノンジャンル〔ノンジャンル〕”}, {totalNum:47121, biggenre:”ファンタジー”, percentage:9.1, genre:”ハイファンタジー〔ファンタジー〕”}, {totalNum:28883, biggenre:”恋愛”, percentage:5.6, genre:”現実世界〔恋愛〕”}, {totalNum:23217, biggenre:”文芸”, percentage:4.5, genre:”ヒューマンドラマ〔文芸〕”}, {totalNum:21320, biggenre:”ファンタジー”, percentage:4.1, genre:”ローファンタジー〔ファンタジー〕”}, {totalNum:17079, biggenre:”恋愛”, percentage:3.3, genre:”異世界〔恋愛〕”}, {totalNum:16798, biggenre:”その他”, percentage:3.2, genre:”その他〔その他〕”}, {totalNum:13892, biggenre:”その他”, percentage:2.6, genre:”詩〔その他〕”}, {totalNum:13341, biggenre:”文芸”, percentage:2.5, genre:”コメディー〔文芸〕”}, {totalNum:10120, biggenre:”文芸”, percentage:1.9, genre:”ホラー〔文芸〕”}, {totalNum:9502, biggenre:”その他”, percentage:1.8, genre:”エッセイ〔その他〕”}, {totalNum:8486, biggenre:”文芸”, percentage:1.6, genre:”純文学〔文芸〕”}, {totalNum:7211, biggenre:”文芸”, percentage:1.3, genre:”アクション〔文芸〕”}, {totalNum:6199, biggenre:”SF”, percentage:1.2, genre:”空想科学〔SF〕”}, {totalNum:5780, biggenre:”その他”, percentage:1.1, genre:”童話〔その他〕”}, {totalNum:3295, biggenre:”文芸”, percentage:0.6, genre:”推理〔文芸〕”}, {totalNum:3217, biggenre:”文芸”, percentage:0.6, genre:”歴史〔文芸〕”}, {totalNum:2606, biggenre:”SF”, percentage:0.5, genre:”VRゲーム〔SF〕”}, {totalNum:1471, biggenre:”SF”, percentage:0.2, genre:”パニック〔SF〕”}, {totalNum:1454, biggenre:”SF”, percentage:0.2, genre:”宇宙〔SF〕”}, {totalNum:190, biggenre:”その他”, percentage:0.0, genre:”リプレイ〔その他〕”}}

on callNarouAPI(aRec, callFrom, callNum)
  set reqURLStr to “http://api.syosetu.com/novelapi/api/” –通常API
  
  
–set aRec to {gzip:”5″, |st|:callFrom as string, out:”json”, lim:callNum as string}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
set aRes to callRestGETAPIAndParseResults(aURL) of me
  
  
set aRESCode to (responseCode of aRes) as integer
  
if aRESCode is not equal to 200 then return false
  
  
set aRESHeader to responseHeader of aRes
  
set aRESTres to (json of aRes) as list
  
end callNarouAPI

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setValue:“gzip” forHTTPHeaderField:“Content-Encoding”
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
  
set rRes to bRes’s gunzippedData() –From GZIP.framework
  
  
set resStr to NSString’s alloc()’s initWithData:rRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to NSArray’s arrayWithArray:aRecList
  
  
set sortDesc to NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
  
set bList to sortedArray as list
  
return bList
end sortRecListByLabel

on getLabelFromNum(aList, labelLIst, aNum)
  set aInd to offsetOf(aList, aNum) of me
  
set anItem to contents of item aInd of labelLIst
  
return anItem
end getLabelFromNum

on offsetOf(aList as list, aTarg)
  set aArray to current application’s NSArray’s arrayWithArray:aList
  
set aIndex to aArray’s indexOfObjectIdenticalTo:aTarg
  
return (aIndex + 1)
end offsetOf

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s doubleValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:aa
  
return (aStr as text) as real
end roundingDownNumStr

on roundingUpNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s doubleValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundUp)
  
set aStr to aFormatter’s stringFromNumber:aa
  
return (aStr as text) as real
end roundingUpNumStr

★Click Here to Open This Script 

2017/10/07 なろう小説APIを呼び出す v2(Zip展開つき)

「小説家になろう」サイトのAPI「なろう小説API」を呼び出してデータを取得するAppleScriptです。

「なろう小説API」は事前にAPI Keyの取得も不要で、簡単に呼び出せるのでお手軽に使えます。用意されているのはデータ取得のメソッドであり、結果の保持や集計、しぼりこみについてはローカル側で勝手に行う放任仕様です条件抽出のパラメータもあります。

ただし、本APIが用意しているZip圧縮を利用するのに少々骨が折れました。

http header中でContent-Encodingに「gzip」を指定する程度では対応できませんでした。Zip圧縮転送指定時(パラメータにgzipを指定)APIから返ってくるデータそのものがZip圧縮されており、APIから返ってきたデータそのもの(NSData)を展開する必要がありました。

そこで、NSDataをそのままZip展開できるオープンソースのフレームワーク「GZIP」(By Nick Lockwood)を利用してみました。同プロジェクトはGithub上のXcodeプロジェクトをXcodeでビルドするとFrameworkが得られるので、ビルドして~/Library/FrameworksフォルダにGZIP.frameworkを入れてください。

とりあえず呼び出して500件のデータを取得していますが、500件ずつ取得してループするように書き換えるとよいでしょう。

AppleScript名:なろう小説API v2(Zip展開つき)
– Created 2017-10-03 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4890
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “GZIP”
–https://github.com/nicklockwood/GZIP
–http://dev.syosetu.com/man/api/
–1日の利用上限は80,000または転送量上限400MByte???

property |NSURL| : a reference to current application’s |NSURL|
property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property NSURLConnection : a reference to current application’s NSURLConnection

set reqURLStr to “http://api.syosetu.com/novelapi/api/” –通常API
–set reqURLStr to “http://api.syosetu.com/novel18api/api/”–18禁API

set aRec to {gzip:“5″, out:“json”, lim:“500″}

set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestGETAPIAndParseResults(aURL) of me

set aRESCode to (responseCode of aRes) as integer
if aRESCode is not equal to 200 then return false

set aRESHeader to responseHeader of aRes
set aRESTres to (json of aRes) as list

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setValue:“gzip” forHTTPHeaderField:“Content-Encoding”
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
  
set rRes to bRes’s gunzippedData() –From GZIP.framework
  
  
set resStr to NSString’s alloc()’s initWithData:rRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

★Click Here to Open This Script 

2017/09/29 macOS 10.13で変更されたPDFアノテーションの作成

macOS 10.13, High Sierraで変更になった、PDF書類へのアノテーション作成を行うAppleScriptです。

とくにサンプルプログラムが見当たらなかったのですが、「まあこんなもんだろー」とあたりをつけて書いてみたら動きました。Objective-CのコードもSwiftのコードも見当たらず、いまのところPDFアノテーション作成コードはこのAppleScriptしか見当たらない状態、、、、、、

選択したPDFに対してアノテーションを追加します。自分は動作検証用に作っためもり入りの「方眼紙PDF」に対してアノテーションを追加してみました(掲載スクリーンショット)。PDFの座標系は左下が原点です。

AppleがmacOS 10.13に作ったScripting BridgeのNSRectの変換ミス(?)のバグに対処してあります。恥かしいレベルのバグだし、まいどまいど腹がたつのでさっさと修正してほしいのですが、いつごろ修正されるものやら。予想以上に今回のバグの被害範囲が広いので、さっさと修正してほしいものです(指定写真の顔の部分をエリア検出してフィルタをかけるといった処理が途中でエラーに。ものすごく腹立たしい)。

circle_annotation_resized.png

従来の書き方でもまだ動くので、今日明日ですぐに切り替えが必要になるわけではなさそうです。

新しい書き方のほうが単純で、さまざまなアノテーションでもだいたい同様に記述できるので、個人的にはこちらのほうがいい感じがします。

→ 指定PDFの最初のページに大量のスクウェアアノテーションを添付する

→ 指定PDFの最初のページにアノテーションを追加する(テキストアノテーション)

AppleScript名:指定PDFの最初のページにサークルアノテーションを追加する(High Sierra)
– Created 2017-09-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.7″ –macOS 10.13 or later
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
–http://piyocast.com/as/archives/4855

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “Choose a PDF”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()
set firstPage to (aPDFdoc’s pageAtIndex:0)

–macOS 10.13以降で指定する形式のAnnotation
set keyList to {current application’s PDFAnnotationKeyColor, current application’s PDFAnnotationKeyInteriorColor}
set valList to {current application’s NSColor’s blackColor(), current application’s NSColor’s grayColor()}
set propDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

–macOS 10.13のバグ対策。NSRectはwidth–>heightの順にパラメータを並べる
set anAnnotation to current application’s PDFAnnotation’s alloc()’s initWithBounds:{origin:{x:100, y:100}, |size|:{width:600, height:600}} forType:(current application’s PDFAnnotationSubtypeCircle) withProperties:propDict

firstPage’s addAnnotation:anAnnotation

aPDFdoc’s writeToFile:aPOSIX

★Click Here to Open This Script 

square_annotation_resized.png

AppleScript名:指定PDFの最初のページにスクウェアアノテーションを追加する(High Sierra)
– Created 2017-09-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.7″ –macOS 10.13 or later
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
–http://piyocast.com/as/archives/4855

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “Choose a PDF”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()
set firstPage to (aPDFdoc’s pageAtIndex:0)

–macOS 10.13以降で指定する形式のAnnotation
set keyList to {current application’s PDFAnnotationKeyColor, current application’s PDFAnnotationKeyInteriorColor}
set valList to {current application’s NSColor’s blackColor(), current application’s NSColor’s grayColor()}
set propDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

–macOS 10.13のバグ対策。NSRectはwidth–>heightの順にパラメータを並べる
set anAnnotation to current application’s PDFAnnotation’s alloc()’s initWithBounds:{origin:{x:100, y:100}, |size|:{width:500, height:500}} forType:(current application’s PDFAnnotationSubtypeSquare) withProperties:propDict

firstPage’s addAnnotation:anAnnotation

aPDFdoc’s writeToFile:aPOSIX

★Click Here to Open This Script 

text_annotation_resized.png

AppleScript名:指定PDFの最初のページにテキストアノテーションを追加する(High Sierra)
– Created 2017-09-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.7″ –macOS 10.13 or later
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
–http://piyocast.com/as/archives/4855

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “Choose a PDF”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()
set firstPage to (aPDFdoc’s pageAtIndex:0)

–macOS 10.13以降で指定する形式のAnnotation
set keyList to {current application’s PDFAnnotationKeyInteriorColor, current application’s PDFAnnotationKeyContents}
set valList to {current application’s NSColor’s yellowColor(), “Piyomaru Software” & return & “ぴよまるソフトウェア”}
set propDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

–macOS 10.13のバグ対策。NSRectはwidth–>heightの順にパラメータを並べる
set anAnnotation to current application’s PDFAnnotation’s alloc()’s initWithBounds:{origin:{x:100, y:100}, |size|:{width:200, height:50}} forType:(current application’s PDFAnnotationSubtypeFreeText) withProperties:propDict

firstPage’s addAnnotation:anAnnotation

aPDFdoc’s writeToFile:aPOSIX

★Click Here to Open This Script 

line_annotation_resized.png

AppleScript名:指定PDFの最初のページにラインアノテーションを追加する(High Sierra)
– Created 2017-09-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.7″ –macOS 10.13 or later
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
–http://piyocast.com/as/archives/4855

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “Choose a PDF”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()
set firstPage to (aPDFdoc’s pageAtIndex:0)

–macOS 10.13以降で指定する形式のAnnotation
set keyList to {current application’s PDFAnnotationKeyColor}
set valList to {current application’s NSColor’s blackColor()}
set propDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

–macOS 10.13のバグ対策。NSRectはwidth–>heightの順にパラメータを並べる
set anAnnotation to current application’s PDFAnnotation’s alloc()’s initWithBounds:{origin:{x:100, y:100}, |size|:{width:600, height:600}} forType:(current application’s PDFAnnotationSubtypeLine) withProperties:propDict

firstPage’s addAnnotation:anAnnotation

aPDFdoc’s writeToFile:aPOSIX

★Click Here to Open This Script 

2017/09/25 書式つきテキストを組み立ててサイズを取得して画像書き出し v2

複数行にわたる書式付きテキストを組み立てて、描画サイズを取得して画像書き出しするAppleScriptです。

スタイル付きテキスト(NSAttributedString)を作成し、その描画エリアのサイズを取得。そんなもん、こんな手軽に求められるとは思ってもみませんでした。

描画エリアのサイズをもとに空白画像を作成して、そこにスタイル付きテキストを描画。結果の画像を指定パス名で書き出します。

test9999.png
▲本Scriptの実行結果(macOS 10.12)。出力用の文字列を長くしても、描画サイズを調べて画像化しているので、途中で途切れたりはしません

ただし、意外なところでOSバージョンによる挙動の差が出ました。書式付きテキストの描画サイズの取得ではなく、下地の塗りつぶし(白)についてです(下図参照)。

1013_strange_behavior.png
▲macOS 10.12(上)とmacOS 10.13GM(下)の本Scriptの実行結果。検証のために文字データを変更(途中の改行を減らしている)

macOS 10.13のRelease Build(Build 17A365)でも同様の結果になりました。macOS 10.10で実験したら10.12と同様の結果に。何か(トンでもない場所の)仕様が変わったのだろうか?

→ macOS 10.13のバグでした。対応版を掲載しています

AppleScript名:書式つきテキストを組み立ててサイズを取得して画像書き出し v2
– Created 2017-09-25 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4843

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSBezierPath : a reference to current application’s NSBezierPath
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSKernAttributeName : a reference to current application’s NSKernAttributeName
property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName
property NSFont : a reference to current application’s NSFont
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSImage : a reference to current application’s NSImage
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSString : a reference to current application’s NSString
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSColor : a reference to current application’s NSColor

set outPath to “~/Desktop/test9999.png”
set fillColor to NSColor’s whiteColor –塗り色
set aFontSize to 48
set aString to “Takaaki” & return & “Naganoya” & return & “長野谷” & return & “隆昌”

set anAssrStr to makeRTFfromParameters(aString, “HiraMinProN-W3″, aFontSize, -2, (aFontSize * 1.5)) of me
set aSize to anAssrStr’s |size|()
–>  {width:408.0, height:187.2}

set attrStrWidth to width of aSize
set attrStrHeight to height of aSize

set {xPos, yPos} to {0, 0}

–下地の画像を作成
set tmpImg1 to makeImageWithFilledColor(attrStrWidth, attrStrHeight, fillColor) of me

–下地の画像の上にAttributed Stringを描画
set tmpImg2 to drawAttributedStringsOnImage(tmpImg1, anAssrStr, xPos, yPos) of me

–PNG形式でファイルに保存
set aRes to saveImageRepAtPathAsPNG(tmpImg2, outPath) of me

–画像のうえに指定のスタイル付きテキストを描画して画像を返す
on drawAttributedStringsOnImage(anImage, anAssrStr, xPos, yPos)
  anImage’s lockFocus()
  
anAssrStr’s drawAtPoint:(current application’s NSMakePoint(xPos, yPos))
  
anImage’s unlockFocus()
  
return anImage
end drawAttributedStringsOnImage

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  –Font
  
set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
–Color
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
–Kerning
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
–Underline
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
–Ligature
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
–Paragraph space
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
end makeRTFfromParameters

–指定サイズの画像を作成し、背景を指定色で塗る
on makeImageWithFilledColor(aWidth, aHeight, fillColor)
  set anImage to NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight))
  
  
anImage’s lockFocus()
  
set theRect to {{x:0, y:0}, {height:aHeight, width:aWidth}}
  
set theNSBezierPath to NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  
fillColor’s |set|()
  
theNSBezierPath’s fill()
  
anImage’s unlockFocus()
  
  
return anImage
end makeImageWithFilledColor

–画像を指定パスにPNG形式で保存
on saveImageRepAtPathAsPNG(anImage, outPath)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep
  
  
set pathString to NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
  
set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value))
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
  
return aRes
end saveImageRepAtPathAsPNG

★Click Here to Open This Script 

2017/08/27 文の意味類似度の評価

Apitore「文の意味類似度の評価」APIを呼び出して、文単位で意味的な近さを評価するAppleScriptです。

ApitoreのREST API呼び出しのために、Apitoreにユーザー登録してAccess Tokenを取得し、retAccessToken()内に記述しておいてください(自分はKeychainにAccessTokenを記録して呼び出すライブラリを利用しています)。

掲載リストそのままでAccess Tokenを記入していない状態だとエラーになります。

「文の意味類似度の評価」APIはWord2Vecを用いているわけですが、Word2Vecの処理のためにWikipediaのダンプ内容を学習させており、ローカルでもWord2Vecの演算を行いたいところ。もう、いっそのことOSにWikipediaのローカルダンプを含めて配布してほしい今日このごろです(OSインストーラのサイズが倍ぐらいになりそうですが)。

AppleScript名:文の意味類似度の評価
– Created 2017-08-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4792

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSMutableData : a reference to current application’s NSMutableData
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSString : a reference to current application’s NSString
property NSURLConnection : a reference to current application’s NSURLConnection
property NSURLComponents : a reference to current application’s NSURLComponents
property NSNumber : a reference to current application’s NSNumber
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set targStr1 to "戦場の絆REV.4において、ガンダムはコスト250の格闘機である"
set targStr2 to "戦場の絆REV.2において、ガンダムはかつてコスト280の近距離機であった"
set aRes to calcSimiliarity(targStr1, targStr2) of me

set targStr3 to "この秋、戦場の絆REV.4にGブルとザクレロが宇宙ステージ専用機体として登場するらしい。"
set targStr4 to "バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011。"
set bRes to calcSimiliarity(targStr3, targStr4) of me

return {aRes, bRes}
–> {0.9, 0.0}–1.0に近いほど類似度が高い。0に近いほど類似度が低い

on calcSimiliarity(targStr1, targStr2)
  set reqURLStr to "https://api.apitore.com/api/53/sentence-similarity/eval"
  
set accessToken to retAccessToken() of me –Access Token
  
  
set aReq to {text1:targStr1, text2:targStr2}
  
set aRec to {access_token:accessToken}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
set aRESTres to (json of aRes)
  
return roundingDownNumStr(aRESTres’s similarity, 2) of me
end calcSimiliarity

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:"POST"
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:"application/json" forHTTPHeaderField:"Content-Type"
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return "xxXXXxxX-XxXx-XXXX-xXXX-XXxXXxxXxxXx" –API Tore Access Token
end retAccessToken

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingDownNumStr

★Click Here to Open This Script 

2017/08/11 日本語形態素解析【新語対応】_ipadic_neologd(POST版)

apitoreのREST API「日本語形態素解析【Neologd対応】」のPOST対応版APIを呼び出すAppleScriptです。

apitore上の既存の形態素解析APIがPOST対応し、1MBまでのサイズのテキストの形態素解析が行えるようになったので、ためしに呼んでみることにしました。

ただし、テキストのサイズが大きくなった場合の処理時間が読めないので、いきなりMAXの1MBのテキストを形態素解析させるのは得策ではないでしょう(自分も様子見中)。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得して、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

個人的にはREST APIのAccess TokenをmacOSのKeychainに入れて、アカウント名とサイト名でKeychainに問い合わせる「keychain Lib」AppleScript Librariesを用いています。

AppleScript名:日本語形態素解析【新語対応】_ipadic_neologd(POST版)
– Created 2017-08-11 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
–use keychainLib : script “keychainLib”
–http://piyocast.com/as/archives/4773

property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableData : a reference to current application’s NSMutableData
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLConnection : a reference to current application’s NSURLConnection
property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents

(*)
tell current application
  set aTargStr to read (choose file) as «class utf8»–Read text as UTF-8
end tell
*)

–2017/7/3 Ver: POST対応。1MBまでのテキストを一気に形態素解析できるようになった
set aTargStr to “「ACE COMBAT INFINITY」3周年記念キャンペーンを実施。記念エンブレムをプレゼント”
set aTargList to paragraphs of aTargStr

set reqURLStr to “https://api.apitore.com/api/7/kuromoji-ipadic/tokenize”
set accessToken to retAccessToken() of me —Access Token
set aReq to {texts:aTargList}
set aRec to {access_token:accessToken}
set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
set aRESCode to responseCode of aRes
set aRESHeader to responseHeader of aRes
set aRESTres to (json of aRes)
return aRESTres as record

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return “XXXXxxxX-xxxx-XXXx-xxxX-XxxxXxXxxXXx” –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/08/06 Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出

Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出するAppleScriptです。

IOBluetooth経由ではなく、system_profiler経由でアクセスしてみました。メーカー名とデバイス種別で抽出できるので、自分がやりたい処理はできるようになったのですが、できればできたで衝撃の事実が発覚!

Apple製の周辺機器として流通しているものの中にも、メーカー名に”Apple”を返さないものがありました。事実、Magic Keyboard 2やMagic Mouse 2は製造元として「Broadcom」を返してきました。

仕様上、これでいいのか疑問が残る仕上がりです。ハードウェアについては十二分に満足していますが、こんなところでミソがつくとは、、、、

BroadcomはBluetoothの通信チップのメーカーであり、周辺機器そのものを製造しているわけではない、と認識していたのですが、気のせいでしょうか?(たぶん、ただしい)

参考までに、身の回りのBluetoothデバイスの種類のメジャー(Major)とマイナー(Minor)の名称をリストアップしておきます。詳細はユーティリティーフォルダ内の「システム情報」アプリケーションでご確認ください。

Magic Keyboard
Major: Peripheral, Minor:Keyboard

Magic Mouse 2
Major: Peripheral, Minor:Mouse

Mac mini
Major: Computer, Minor:Desktop

Bluetooth Speaker
Major: Audio, Minor:Loudspeaker

AirPods
Major: Audio, Minor:Headphones

iPad mini
Major: Computer, Minor:Handheld

iPhone
Major: Phone, Minor:Smartphone

本ルーチンについては、「意味なし予約語」を用いて英単語でそれっぽく表記できるようにしてみましたが、一般的なサブルーチンのハンドラの表記形式を用いても問題はありません。たまたまです。

AppleScript名:Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出
– Created 2017-08-06 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4764

property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSPropertyListSerialization : a reference to current application’s NSPropertyListSerialization
property NSPropertyListImmutable : a reference to current application’s NSPropertyListImmutable
property NSPropertyListFormat : a reference to current application’s NSPropertyListFormat
property NSPredicate : a reference to current application’s NSPredicate

–製造者が”Apple”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral from “Apple”
–>  {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-51, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}, {device_supportsESCO:”attrib_No”, device_manufacturer:”Apple (0×3, 0×31C)”, device_ispaired:”attrib_Yes”, device_services:”Apple Wireless Mouse”, device_isconnected:”attrib_No”, device_majorClassOfDevice_string:”Peripheral”, device_isNormallyConnectable:”attrib_Yes”, device_isconfigured:”attrib_Yes”, device_addr:”XX-XX-XX-XX-XX-XX”, device_productID:”0×030D”, device_supportsSSP:”attrib_No”, device_vendorID:”0×05AC”, device_classOfDevice:”0×05 0×20 0×2580″, device_minorClassOfDevice_string:”Mouse”, device_fw_version:”0×0084″, device_supportsEDR:”attrib_No”}}
–Appleのデバイスでも製造者がAppleになっていないものもある。Magic Keyboard 2とか

–種類(マイナー)が “Headphones”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral about “Headphones”
–>  {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-51, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}}

–製造者が”Apple”で、種類(マイナー)が “Headphones”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral from “Apple” about “Headphones”
–> {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-52, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}}

on returnBTPeripheral from devMaker as string : “” about kindName as string : “”
  set sRes to do shell script “/usr/sbin/system_profiler SPBluetoothDataType -detailLevel full -xml”
  
set aSource to (readPlistFromStr(sRes) of me) as list
  
set aaList to contents of first item of aSource
  
  
set resArray to NSMutableArray’s new()
  
  
set aList to _items of aaList
  
repeat with i in aList
    set aDict to (NSMutableDictionary’s dictionaryWithDictionary:(contents of i))
    
set aKeyList to (aDict’s allKeys()) as list
    
    
set dResList to (aDict’s valueForKeyPath:“device_title”)
    
repeat with ii in dResList
      set dKeyList to ii’s allKeys()
      
set dKey to first item of dKeyList
      
set dDic to (ii’s valueForKeyPath:dKey)
      
      
if devMaker is not equal to “” and kindName is not equal to “” then
        set qText to “device_manufacturer contains ’” & devMaker & “’ && device_minorClassOfDevice_string ==’” & kindName & “’”
      else if devMaker is not equal to “” then
        set qText to “device_manufacturer contains ’” & devMaker & “’”
      else if kindName is not equal to “” then
        set qText to “device_minorClassOfDevice_string ==’” & kindName & “’”
      end if
      
      
set dRes to filterRecListByLabel(dDic, qText) of me
      
if (dRes as list) is not equal to {} then
        (resArray’s addObject:(first item of dRes))
      end if
    end repeat
  end repeat
  
  
return resArray as list
end returnBTPeripheral

–stringのplistを読み込んでRecordに
on readPlistFromStr(theString)
  set aSource to NSString’s stringWithString:theString
  
set pListData to aSource’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aPlist to NSPropertyListSerialization’s propertyListFromData:pListData mutabilityOption:(NSPropertyListImmutable) |format|:(NSPropertyListFormat) errorDescription:(missing value)
  
return aPlist
end readPlistFromStr

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel(aRecList as list, aPredicate as string)
  set aArray to NSArray’s arrayWithArray:aRecList
  
  
set aPredicate to NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
  
set bList to filteredArray as list
  
return bList
end filterRecListByLabel

★Click Here to Open This Script 

2017/07/23 指定の画像をASCII ART化してTextEditでオープン v3

jp2aを利用して指定の画像を色付きHTML形式のアスキーアート化して出力し、HTMLをRTFに変換してTextEditでオープンしてフォントを変更するAppleScriptの改良版です。

指定のJPEG画像をアスキーアート化するjp2aは、Homebrew経由でインストールしてください。

asciiart_gc1_resized.png

画像種別をとくに問わないように対応してみました。ただし、PNG画像については背景が透過している場合には画像余白トリミングフレームワーク「KGPixelBoundsClipKit」を用いてトリミングし、JPEG画像に変換します。

最大の変更点は、TextEditに対して命令を投げてフォントを指定していたものから、NSMutableAttributedStringに対してCocoaの機能を利用してフォント種別を指定するように変更したところです(意外と翻訳元となるObjective-Cの記述例が見つからない)。

テストする際には、KGPixelBoundsClipKitフレームワークのバイナリを~/Library/Frameworksフォルダに入れてお試しください。

–> Download Framework Binary

AppleScript名:指定の画像をASCII ART化してTextEditでオープン v3
– Created 2017-07-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “KGPixelBoundsClipKit” –https://github.com/kgn/KGPixelBoundsClip
–http://piyocast.com/as/archives/4740

set targFontName to “Osaka-Mono” –”Courier New”

set aFile to choose file of type {“public.image”}
tell application “System Events”
  set aType to type identifier of aFile –get UTI
end tell

if aType is equal to “public.png” then
  –PNGの場合、画像の余白をトリミング(背景が透過している場合にかぎる)
  
set anImage to (current application’s NSImage’s alloc()’s initWithContentsOfFile:(POSIX path of aFile))
  
set bImage to anImage’s imageClippedToPixelBounds()
  
  
–トリミング結果をデスクトップにJPEG形式で保存
  
set aDesktopPath to (current application’s NSProcessInfo’s processInfo()’s environment()’s objectForKey:(“HOME”))’s stringByAppendingString:“/Desktop/”
  
set savePath to aDesktopPath’s stringByAppendingString:((current application’s NSUUID’s UUID()’s UUIDString())’s stringByAppendingString:“.jpg”)
  
set fRes to saveNSImageAtPathAsJPG(bImage, savePath, 100) of me
  
set aaFile to (POSIX file (savePath as string)) as alias
  
else if aType is equal to “public.jpeg” then
  –JPEGの場合
  
copy aFile to aaFile
  
else
  –PNGとJPEG以外の場合には読み込んでJPEGに変換
  
set aImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:(POSIX path of aFile)
  
set fRes to retUUIDfilePath(POSIX path of aFile, “jpg”) of me
  
set sRes to saveNSImageAtPathAsJPG(aImage, fRes, 100) of me
  
set aaFile to (POSIX file (fRes as string)) as alias
  
end if

–ASCII ARTに変換してHTMLとして出力
set htmlRes to jpegToAsciiArt(aaFile, 120) of me

–出力されたHTMLデータを評価してスタイル付きテキストに変換
set htmlData to current application’s NSString’s stringWithString:htmlRes
set keyList to {current application’s NSDocumentTypeDocumentAttribute, current application’s NSCharacterEncodingDocumentAttribute}
set valList to {current application’s NSHTMLTextDocumentType, current application’s NSUTF8StringEncoding}
set optDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

set aStyledStr to current application’s NSMutableAttributedString’s alloc()’s initWithData:(htmlData’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)) options:optDict documentAttributes:(missing value) |error|:(missing value)

–フォント種別およびサイズを指定してみた
set aRange to current application’s NSMakeRange(0, aStyledStr’s |length|())
set aVal1 to current application’s NSFont’s fontWithName:targFontName |size|:11
aStyledStr’s beginEditing()
aStyledStr’s addAttribute:(current application’s NSFontAttributeName) value:aVal1 range:aRange
aStyledStr’s endEditing()

–スタイル付きテキストをRTFとしてデスクトップに保存
set targFol to POSIX path of (path to desktop)
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
set bRes to my saveStyledTextAsRTF(aUUID, targFol, aStyledStr) –PDFで書き出す
set newPath to targFol & aUUID & “.rtf”

–Macの画面(メインスクリーン)の高さを取得する
set dRec to ((current application’s NSScreen’s mainScreen())’s deviceDescription()’s NSDeviceSize) as record
set dHeight to (height of dRec) as integer

–保存したRTFをTextEditでオープンして等幅フォント設定して、Window横幅を変更
tell application “TextEdit”
  activate
  
open (POSIX file newPath) as alias
  
  
tell window 1
    set {x1, y1, x2, y2} to bounds
    
set bounds to {x1, y1, (x1 + 1024), dHeight}
  end tell
  
  
tell document 1
    save
  end tell
end tell

–指定ファイルパスと同一階層にファイル名をUUID、拡張子を指定したものを作成して返す
on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (current application’s NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((current application’s NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

–NSImageを指定パスにJPEG形式で保存
on saveNSImageAtPathAsJPG(anImage, outPath, qulityNum as real)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to current application’s NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to current application’s NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(current application’s NSJPEGFileType) |properties|:{NSImageCompressionFactor:qulityNum})
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsJPG

–与えられたファイルパスのJPEG画像をASCII ARTに変換して返す
on jpegToAsciiArt(anImageAlias, digitNum as integer)
  set sText to “/usr/local/bin/jp2a -f –html-raw –colors –width=” & (digitNum as string) & ” -i “ & quoted form of POSIX path of anImageAlias
  
set tRes to do shell script sText
  
return tRes
end jpegToAsciiArt

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName, targFol, aStyledString)
  set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to current application’s NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(current application’s NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
set theName to current application’s NSString’s stringWithString:aFileName
  
set theName to theName’s stringByReplacingOccurrencesOfString:“/” withString:“_”
  
set theName to theName’s stringByReplacingOccurrencesOfString:“:” withString:“_”
  
set thePath to current application’s NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

★Click Here to Open This Script 

2017/07/21 指定のPNG画像をASCII ART化してTextEditでオープン v2

jp2aを利用して指定のPNG画像を色付きHTML形式のアスキーアート化して出力し、HTMLをRTFに変換してTextEditでオープンしてフォントを変更するAppleScriptです。

指定のJPEG画像をアスキーアート化するjp2aは、Homebrew経由でインストールしてください。

asciiart4_resized.png

asciiart3_resized.png
▲このように縦長で余白の多い画像から、自動で余白部分をトリミングして処理

asciiart_z.png
▲左側はPNG画像の余白トリミング処理を行ったもの、右側はトリミング処理を行わなかったもの

アスキーアート化するにあたって、元画像の余白部分をトリミングできたほうが良好な結果が得られるため、処理対象をJPEG画像からPNG画像(背景透過)に変更し、以前に使った画像余白トリミングフレームワーク「KGPixelBoundsClipKit」を用いてトリミングしてJPEG画像に変換。

トリミングしたJPEG画像をjp2aでHTMLのアスキーアートに変換し、さらにHTMLをスタイル付きテキスト(NSAttributedString)に変換。

スタイル付きテキストをRTF(リッチテキストフォーマット)に変換してファイル出力。RTFになればTextEditで扱えるので、TextEditでオープンして本文のフォントを等幅フォント(Osaka-mono)に指定してウィンドウのサイズを変更しています。

テストする際には、KGPixelBoundsClipKitフレームワークのバイナリを~/Library/Frameworksフォルダに入れてお試しください。

–> Download Framework Binary

AppleScript名:指定のPNG画像をASCII ART化してTextEditでオープン v2
– Created 2017-07-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “KGPixelBoundsClipKit” –https://github.com/kgn/KGPixelBoundsClip
–http://piyocast.com/as/archives/4736

set aFile to choose file of type {“public.png”}

–PNG画像の余白をトリミング
set anImage to (current application’s NSImage’s alloc()’s initWithContentsOfFile:(POSIX path of aFile))
set bImage to anImage’s imageClippedToPixelBounds()

–トリミング結果をデスクトップにJPEG形式で保存
set aDesktopPath to (current application’s NSProcessInfo’s processInfo()’s environment()’s objectForKey:(“HOME”))’s stringByAppendingString:“/Desktop/”
set savePath to aDesktopPath’s stringByAppendingString:((current application’s NSUUID’s UUID()’s UUIDString())’s stringByAppendingString:“.jpg”)
set fRes to saveNSImageAtPathAsJPG(bImage, savePath, 100) of me

–ASCII ARTに変換してHTMLとして出力
set anAlias to (POSIX file (savePath as string)) as alias
set htmlRes to jpegToAsciiArt(anAlias, 120) of me

–出力されたHTMLデータを評価してスタイル付きテキストに変換
set htmlData to current application’s NSString’s stringWithString:htmlRes
set keyList to {current application’s NSDocumentTypeDocumentAttribute, current application’s NSCharacterEncodingDocumentAttribute}
set valList to {current application’s NSHTMLTextDocumentType, current application’s NSUTF8StringEncoding}
set optDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
set aStyledStr to current application’s NSAttributedString’s alloc()’s initWithData:(htmlData’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)) options:optDict documentAttributes:(missing value) |error|:(missing value)

–スタイル付きテキストをRTFとしてデスクトップに保存
set targFol to POSIX path of (path to desktop)
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
set bRes to my saveStyledTextAsRTF(aUUID, targFol, aStyledStr) –PDFで書き出す
set newPath to targFol & aUUID & “.rtf”

–保存したRTFをTextEditでオープンして等幅フォント設定して、Window横幅を変更
tell application “TextEdit”
  activate
  
open (POSIX file newPath) as alias
  
  
tell text of document 1
    set font of every attribute run to “Osaka-Mono”
  end tell
  
  
tell window 1
    set {x1, y1, x2, y2} to bounds
    
set bounds to {x1, y1, (x1 + 800), y2}
  end tell
end tell

–NSImageを指定パスにJPEG形式で保存
on saveNSImageAtPathAsJPG(anImage, outPath, qulityNum as real)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to current application’s NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to current application’s NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(current application’s NSJPEGFileType) |properties|:{NSImageCompressionFactor:qulityNum})
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsJPG

–与えられたファイルパスのJPEG画像をASCII ARTに変換して返す
on jpegToAsciiArt(anImageAlias, digitNum as integer)
  set sText to “/usr/local/bin/jp2a -f –html-raw –colors –width=” & (digitNum as string) & ” -i “ & quoted form of POSIX path of anImageAlias
  
set tRes to do shell script sText
  
return tRes
end jpegToAsciiArt

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName, targFol, aStyledString)
  –Convert NSMutableStyledStrings to RTF
  
set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to current application’s NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(current application’s NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
– build path based on title
  
set theName to current application’s NSString’s stringWithString:aFileName
  
set theName to theName’s stringByReplacingOccurrencesOfString:“/” withString:“_”
  
set theName to theName’s stringByReplacingOccurrencesOfString:“:” withString:“_”
  
set thePath to current application’s NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

★Click Here to Open This Script 

2017/07/21 指定のJPEG画像をASCII ART(カラーhtml)化してクリップボードに転送

jp2aを利用して指定のJPEG画像を色付きHTML形式のアスキーアート化して出力し、HTMLをスタイル付きテキストに変換してクリップボードに入れるAppleScriptです。

指定のJPEG画像をアスキーアート化するjp2aは、Homebrew経由でインストールしてください。

Terminal.app上で、

  sudo brew install jp2a

でインストールできます。

HTML形式で出力したASCII ARTをスタイル付きテキストに変換するあたりにCocoaの機能(NSAttributedString)を用いています。

実行するとアスキーアート化する対象のJPEG画像を選択。クリップボードに書式付きテキストの状態で入るため、書式付きテキスト対応のエディタ(テキストエディットなど)にペーストしてください。

asciiart1_resized.png

asciiart2_resized.png

AppleScript名:指定のJPEG画像をASCII ART(カラーhtml)化してクリップボードに転送
– Created 2017-07-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4733

set aFile to choose file of type {“public.jpeg”}
set htmlRes to jpegToAsciiArt(aFile, 100) of me
set htmlData to current application’s NSString’s stringWithString:htmlRes

set keyList to {current application’s NSDocumentTypeDocumentAttribute, current application’s NSCharacterEncodingDocumentAttribute}
set valList to {current application’s NSHTMLTextDocumentType, current application’s NSUTF8StringEncoding}

set optDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList

set aStyledStr to current application’s NSAttributedString’s alloc()’s initWithData:(htmlData’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)) options:optDict documentAttributes:(missing value) |error|:(missing value)

set attrList to {aStyledStr}
restoreClipboard(attrList) of me

–与えられたファイルパスのJPEG画像をASCII ARTに変換して返す
on jpegToAsciiArt(anImageAlias, digitNum as integer)
  set sText to “/usr/local/bin/jp2a -f –html-raw –colors –width=” & (digitNum as string) & ” -i “ & quoted form of POSIX path of anImageAlias
  
set tRes to do shell script sText
  
return tRes
end jpegToAsciiArt

–set the clipboard to….
on restoreClipboard(theArray as list)
  set thePasteboard to current application’s NSPasteboard’s generalPasteboard()
  
thePasteboard’s clearContents()
  
thePasteboard’s writeObjects:theArray
end restoreClipboard

★Click Here to Open This Script 

2017/07/05 Numbersでアクセス解析データを合計 v3

Numbersで表示している最前面の書類のすべてのシート上にある表のデータを集計するAppleScriptです。

4年分ぐらいのBlogのアクセス集計を行うことになり、ページ単位のアクセス情報をNumbers上にペースト。

numbers1.png

月ごとにシートを分けて保存し、「アクセスURL」「アクセス回数」のデータを分析することにしました。データは月ごとにシートに分けているけれども、分析を行う際にはすべてをまとめる必要がある、というのと、Numbersにそんな便利な機能はないというのがミソ。こういう用途こそAppleScriptで自動化すべきものです。

アクセス解析ページのデータをNumbersにコピペで貼り付けて、集計自体はAppleScriptで実行。

Numbersからデータを取得する部分は割とさくっとできたのですが、問題は集計部分。

Cocoaの機能を使わないと処理が大変なことはわかっていたので、とりあえずNSCountedSetにページのURLを突っ込んで集計を試みましたが、NSCountedSetを他のデータから作るのは割と融通がききませんでした。

“A”が10回存在するというデータをNSCountedSetに突っ込もうとすると、

  {”A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”}

という配列を作って追加しないといけないようだったので、1,000回だったら1,000個のデータを含む配列を作って……とやっていたら、要素数が100万個ぐらいの巨大な配列を作ることになり、処理時間が余計にかかってしまいました(逆に、メモリ8Gバイトのマシンでよく動いたものだと)。

結局、このバージョンのように集計時には1つの巨大なレコードにページのURLとアクセス回数を記録するように変更しました。

   {”/path/to/url1″:1024, “/path/to/url2″:311…….}

このあたり、AppleScriptのレコードではURLそのものをラベルにすることは(許容されない特殊文字を含んでいるため)不可能ですが、CocoaのNSDictionary(正確にはNSMutableDictionary)であればこのようなラベルを持てるので、便利に使っています。

集計後にURLと回数を個別に取り出して配列に入れ、回数で降順ソート。

  {{accessCount:300, aURL:”/path/to/url1″}, {accessCount:200, aURL:”/path/to/url2″}}

NSCountedSetで集計していたときには80秒ぐらいかかっていましたが、NSDictionaryで集計したら7秒程度(MacBook Pro Retina 2012)で終了しました。

集計元のNumbers書類(34シート、各シートに表1つ。表の行数はだいたい1,000行ぐらい)から集計対象データを取り出す部分は4秒ぐらいなので、集計部分は3秒程度かかっています。

このAppleScript自体はそれほど汎用性があるわけではありませんが、Numbersに入れたデータをAppleScriptから集計するのも、Cocoaの機能を利用すれば割と問題なく行えるという「見本」であります。

AppleScript名:Numbersでアクセス解析データを合計 v3
– Created 2017-07-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4716

script spd
  property allData : {}
end script

–初期化
set (allData of spd) to {}

–Numbers書類上の各シートの表の所定の範囲からデータを取り出す
tell application “Numbers”
  set dCount to count every document
  
if dCount = 0 then return
  
  
tell front document
    set sCount to count every sheet
    
repeat with i from 1 to sCount
      tell sheet i
        tell table 1
          set aRowCount to row count
          
set aRange to range (“A2:B” & (aRowCount as string))
          
set aDat to value of every cell of aRange
          
set (allData of spd) to (allData of spd) & aDat
        end tell
      end tell
    end repeat
  end tell
end tell

–取り出したデータを集計(1つの巨大なRecordに動的に項目を作成しつつ集計)
set aLen to length of (allData of spd)
set aDic to current application’s NSMutableDictionary’s alloc()’s init()

repeat with i from 1 to aLen by 2
  set aKey to contents of item i of (allData of spd)
  
set newVal to contents of item (i + 1) of (allData of spd)
  
  
—解析先ディレクトリをしぼりこみ
  
if aKey begins with “/as/archives/” then
    set aValue to (aDic’s valueForKey:aKey)
    
if aValue = missing value then
      (aDic’s setObject:newVal forKey:aKey) –新規エントリ作成
    else
      (aDic’s setObject:(newVal + aValue) forKey:aKey) –加算
    end if
  end if
end repeat

–集計したデータを個別要素に分離し、アクセス回数でソート
set anArray to current application’s NSMutableArray’s alloc()’s init()
set allKeys to (aDic’s allKeys()) as list

repeat with i in allKeys
  set bVal to (aDic’s valueForKey:i)
  
set bDic to {accessCount:(bVal as integer), aURL:i}
  (
anArray’s addObject:bDic)
end repeat

set bList to sortRecListByLabel(anArray, “accessCount”, false) of me –降順ソート
return bList as list

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList, aLabelStr as string, ascendF as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
  
return sortedArray
end sortRecListByLabel

★Click Here to Open This Script 

2017/05/27 書式つきテキストを組み立てて、クリップボードに設定(影をつける)

書式つきテキスト(NSAttributedString)を組み立てて、クリップボードに設定するAppleScriptの改良版です。影(NSShadow)の追加指定を行ってみました。

stylet2.png

AppleScript名:書式つきテキストを組み立てて、クリップボードに設定(影をつける)
– Created 2017-05-26 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
–http://piyocast.com/as/archives/4664

set anAttrStr to makeRTFfromParameters("ぴよまるソフトウェア", 2.0, 36) of me

set attrList to {anAttrStr}
restoreClipboard(attrList) of me –set the clipboard to rtf_data

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, outlineNum as real, aFontSize as real)
  –フォント
  
set aVal1 to current application’s NSFont’s fontWithName:"HiraKakuStdN-W8" |size|:aFontSize
  
set aKey1 to (current application’s NSFontAttributeName)
  
  
–色
  
set aVal2 to current application’s NSColor’s blackColor()
  
set aKey2 to (current application’s NSForegroundColorAttributeName)
  
  
–カーニング
  
set aVal3 to -2.0
  
set akey3 to (current application’s NSKernAttributeName)
  
  
–アンダーライン
  
set aVal4 to 0
  
set akey4 to (current application’s NSUnderlineStyleAttributeName)
  
  
–リガチャ
  
set aVal5 to 2 –全てのリガチャを有効にする
  
set akey5 to (current application’s NSLigatureAttributeName)
  
  
–枠線(アウトライン)
  
set aVal6 to outlineNum
  
set akey6 to (current application’s NSStrokeWidthAttributeName)
  
  
–Shadow
  
set aVal7 to current application’s NSShadow’s alloc()’s init()
  
aVal7’s setShadowOffset:(current application’s CGSizeMake(1.0, 1.0)) –影のサイズ
  
aVal7’s setShadowColor:(current application’s NSColor’s blackColor()) –影の色
  
aVal7’s setShadowBlurRadius:2.0 –ぼかしの半径
  
set akey7 to (current application’s NSShadowAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey6, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aVal6, aVal7}
  
set attrsDictionary to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to current application’s NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
  
end makeRTFfromParameters

–From Shane Stanley’s Book "Everyday AppleScriptObjC"
on restoreClipboard(theArray as list)
  set thePasteboard to current application’s NSPasteboard’s generalPasteboard()
  
thePasteboard’s clearContents()
  
thePasteboard’s writeObjects:theArray
end restoreClipboard

★Click Here to Open This Script 

2017/05/26 書式つきテキストを組み立てて、クリップボードに設定

書式つきテキスト(NSAttributedString)を組み立てて、クリップボードに設定するAppleScriptです。

作成した書式つきテキストは、そのままKeynoteなどのアプリケーションにペーストして利用することが可能です。グラフィックではないため、アプリケーション上での再編集や影の指定、色の変更などが可能です。

styles.png

Keynoteでアウトライン文字を使いたいことはままあるので、このように手軽にクリップボードに転送して利用できると実用性が高いことでしょう。

後日談:という紹介をしたら、KeynoteのヘビーユーザーからKeynoteのGUI上からアウトライン文字の書式を指定できることを教えてもらいました。

AppleScript名:書式つきテキストを組み立てて、クリップボードに設定
– Created 2017-05-26 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4662

set anAssrStr to makeRTFfromParameters(“ぴよまるソフトウェア”, 3.0, 72) of me

set attrList to {anAssrStr}
restoreClipboard(attrList) of me –set the clipboard to rtf_data

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, outlineNum as real, aFontSize as real)
  –フォント
  
set aVal1 to current application’s NSFont’s fontWithName:“HiraKakuStdN-W8″ |size|:aFontSize
  
set aKey1 to (current application’s NSFontAttributeName)
  
  
–色
  
set aVal2 to current application’s NSColor’s blueColor()
  
set aKey2 to (current application’s NSForegroundColorAttributeName)
  
  
–カーニング
  
set aVal3 to -2.0
  
set akey3 to (current application’s NSKernAttributeName)
  
  
–アンダーライン
  
set aVal4 to 0
  
set akey4 to (current application’s NSUnderlineStyleAttributeName)
  
  
–リガチャ
  
set aVal5 to 2 –全てのリガチャを有効にする
  
set akey5 to (current application’s NSLigatureAttributeName)
  
  
–枠線(アウトライン)
  
set aVal6 to outlineNum
  
set akey6 to (current application’s NSStrokeWidthAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey6}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aVal6}
  
set attrsDictionary to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to current application’s NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
  
end makeRTFfromParameters

–From Shane Stanley’s Book “Everyday AppleScriptObjC”
on restoreClipboard(theArray as list)
  set thePasteboard to current application’s NSPasteboard’s generalPasteboard()
  
thePasteboard’s clearContents()
  
thePasteboard’s writeObjects:theArray
end restoreClipboard

★Click Here to Open This Script 

2017/05/15 感情推定(極性判定) v2

apitoreのREST API「感情推定(極性判定) v2」を呼び出すAppleScriptです。

400文字以下のテキストを解析して、極性(感情)を判定します。ただし、文章が短すぎると判定しづらくなる傾向があるようです。これまでの感情推定APIではpositive/negativeとして判定する傾向があったため、このv2 APIではneutralとして判定する幅を広めにとってもらいました。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得し、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

AppleScript名:感情推定(極性判定) v2
– Created 2017-05-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4639

set aString to "このePubの考え方にそぐわない文章要素がいろいろある。代表的なものは「表」だ。データを掲載しておくのに、「表」は便利な存在だ。書く方からしても、いちいち長々とした文章で説明しなくていいし、読む方からしても整理された情報を読めるわけで、とくにお年寄りに好まれる。ところが、この「表」がePubの出版物にそぐわない。表の幅は変わってほしくないし、文字サイズが大きくなって数ページにわたって表示されるといったことは好ましくない。"
set a1Res to getSentimentFromString(aString) of me
–>  "positive"(なのか?)

on getSentimentFromString(aString)
  set reqURLStr to "https://api.apitore.com/api/39/sentiment-v2/predict"
  
set accessToken to retAccessToken() of me
  
set aRec to {access_token:accessToken, |text|:aString}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestGETAPIAndParseResults(aURL) of me
  
  
set aRESCode to (responseCode of aRes) as integer
  
if aRESCode is not equal to 200 then return false
  
  
set aRESTres to ((json of aRes)’s valueForKeyPath:"predict.sentiment") as string
  
return aRESTres
end getSentimentFromString

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:"GET"
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:"application/json" forHTTPHeaderField:"Accept"
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return "xxXXXxxX-XxXx-XXXX-xXXX-XXxXXxxXxxXx" –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/05/06 Bing Web Search APIでWebキーワード検索

MicrosoftのBing Web Search APIを呼び出してWebのキーワード検索を行うAppleScriptです。

Microsoft Cognitive Servicesのサイトで、開発者アカウント登録を行えば、本APIについては1か月に1,000回まで無料で使用できます(1秒間に7回までという制限もあります)。

アカウント登録を行い、Bing Web SearchをEnableに設定して、本AppleScriptの末尾のretAPIKey()ハンドラ内にAPI Key 1を入力してください(API Keyを記入したScriptを決してそのまま第三者に配布しないでください。あくまでテストと確認用です)。

個人的にBingは検索エンジンとして常用していません。Google検索にくらべて見つかる情報量が少ないと実感していることがその理由です。そのため、Bingの検索エンジンを呼び出せてもあまりメリットを見出せないところ。あくまで実験の一環です。

AppleScript名:Bing Web Search APIでWebキーワード検索
– Created 2017-05-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4634

set aStr to “戦場の絆”

set reqURLStr to “https://api.cognitive.microsoft.com/bing/v5.0/search” –Microsoft Project Oxford
set myAPIkey to retAPIkey() of me –API Primary Key

set aRec to {q:aStr, |count|:“5″, |offset|:“0″, mkt:“ja-JP”}
set bURL to retURLwithParams(reqURLStr, aRec) of me
set aRes to callRestGETAPI(bURL, myAPIkey) of me
set aRESTres to json of aRes
set aRESCode to responseCode of aRes
set aRESHeader to responseHeader of aRes

return (webPages of aRESTres)

–POST methodのREST APIを画像をアップロードしつつ呼ぶ
on callRestGETAPI(aURL, anAPIkey)
  –Request
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:anAPIkey forHTTPHeaderField:“Ocp-Apim-Subscription-Key”
  
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
  
–Parse Results
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
if dRes = missing value then
    set resCode to -1
    
set resHeaders to {}
  else
    set resCode to (dRes’s statusCode()) as integer
    
–Get Response Header
    
set resHeaders to (dRes’s allHeaderFields()) as record
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestGETAPI

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAPIkey()
  return “XxXxXXxXxXxXXxXXxXXXxXXXxxxxXxxX” –API Key 1
end retAPIkey

★Click Here to Open This Script 

2017/05/04 Analyze Imageで画像認識

MicrosoftのCognitive ServicesのREST APIのひとつ、Computer Vision APIの「Analyze Image」を呼び出して画像認識するAppleScriptです。

Microsoft Cognitive Servicesのサイトで、開発者アカウント登録を行えば、本APIについては1か月に5,000回まで無料で使用できます(1分間に20回までという制限もあります)。

アカウント登録を行い、Analyze ImageをEnableに設定して、本AppleScriptの末尾のretAPIKey()ハンドラ内にAPI Key 1を入力してください(API Keyを記入したScriptを決してそのまま第三者に配布しないでください。あくまでテストと確認用です)。

指定した画像のカテゴリ(Categories)、タグ情報(Tags)、詳細内容(Description)の情報を取得しています。

精度については、実際にテスト画像と得られた認識データを見比べていただくしかありません。カタツムリとか誕生日ケーキとか夕日や花火、ワインボトルなど(!)、驚くほど正確に認識されています。夕日の手前にあるのは飛行機ではないのですが、そのぐらいの誤差はあります(イルカを「犬」と判定されたりもしました)。

ためしに、この結果(Description)をそのままGoogle Tlanstlate APIに渡して日本語訳させ画像のFinderコメント欄に入れる実験などもやってみたところ、面白い結果が得られました(実用性はまだまだですが、言いたいことはわかる、というレベル。個人的には英語のままのほうがわかりやすいかも)。

こうしたサービスを利用して、写真.app(Photos.app)内の写真にすべてタグ付けして、タグをもとに風景の写真だけを新規アルバムに入れて分類するといった使い方はできそうです(すでにやってるし)。そして、AppleのアプリケーションとMicrosoftやGoogleのWebサービスを連携させるといった使い方はAppleが提供するわけがないので、こういう用途こそAppleScriptで勝手に連携させるべきだと思います。

r0010704_resized.png
{{metadata:{width:2592, |format|:”Jpeg”, height:1944}, tags:{{|name|:”fireworks”, confidence:0.998994648457}, {|name|:”outdoor object”, confidence:0.998748064041}}, categories:{{|name|:”dark_fireworks”, score:0.99609375}}, |description|:{tags:{”fireworks”, “object”}, captions:{{|text|:“a close up of fireworks”, confidence:0.233880494749}}}, requestId:”ce5e5c15-f68f-4647-9694-5fb8ede9cd31″}}

r0015213_resized.png
{{metadata:{width:2592, |format|:”Jpeg”, height:1944}, tags:{{|name|:”ground”, confidence:0.999917149544}, {|name|:”animal”, confidence:0.993541717529}, {|name|:”invertebrate”, hint:”animal”, confidence:0.952201843262}, {|name|:”outdoor”, confidence:0.887740492821}, {|name|:”mollusk”, hint:”animal”, confidence:0.814713895321}, {|name|:”snail”, hint:”animal”, confidence:0.649717211723}}, categories:{{|name|:”abstract_texture”, score:0.6015625}}, |description|:{tags:{”animal”, “outdoor”, “shellfish”, “snail”, “piece”, “laying”, “food”, “sitting”, “top”, “surface”, “lying”, “banana”, “close”, “fruit”, “bird”, “beach”, “plate”, “board”, “street”}, captions:{{|text|:“a snail on the ground”, confidence:0.480042590526}}}, requestId:”b290dc83-0e33-41b0-8a39-76c067bcffb8″}}

r0015918_resized.png
{{metadata:{width:2048, |format|:”Jpeg”, height:1536}, tags:{{|name|:”grass”, confidence:0.99923813343}, {|name|:”outdoor”, confidence:0.997150361538}, {|name|:”ground”, confidence:0.972813665867}, {|name|:”standing”, confidence:0.93672734499}, {|name|:”animal”, confidence:0.917161226273}, {|name|:”bird”, confidence:0.824012756348}}, categories:{{|name|:”animal_horse”, score:0.98046875}}, |description|:{tags:{”grass”, “outdoor”, “standing”, “bird”, “animal”, “water”, “field”, “white”, “small”, “walking”, “front”, “sitting”, “parrot”, “brown”, “large”, “grassy”, “green”, “body”, “dirt”, “red”, “river”}, captions:{{|text|:“a bird that is standing in the grass”, confidence:0.859258793309}}}, requestId:”bc5b8778-cf96-4d3c-8ba9-be22b38c36dd”}}

img_2502_resized.png
{{metadata:{width:3264, |format|:”Jpeg”, height:2448}, tags:{{|name|:”sky”, confidence:0.999066531658}, {|name|:”outdoor”, confidence:0.99786490202}, {|name|:”sunset”, confidence:0.880310297012}, {|name|:”distance”, confidence:0.229086950421}}, categories:{{|name|:”outdoor_”, score:0.0078125}, {|name|:”outdoor_waterside”, score:0.5234375}}, |description|:{tags:{”outdoor”, “sunset”, “airplane”, “plane”, “sitting”, “building”, “runway”, “large”, “front”, “water”, “pier”, “top”, “sun”, “track”, “orange”, “light”, “road”, “bridge”, “standing”, “train”, “man”, “jet”, “white”, “city”, “riding”, “red”, “flying”, “bird”, “blue”, “night”, “river”, “tower”, “tarmac”}, captions:{{|text|:“a plane that is in front of a sunset”, confidence:0.493561860724}}}, requestId:”49705b7d-99cb-4234-9af6-b30a32f02ad5″}}

img_0313_resized.png
{{metadata:{width:4032, |format|:”Jpeg”, height:3024}, tags:{{|name|:”indoor”, confidence:0.929384291172}, {|name|:”candle”, confidence:0.877458691597}, {|name|:”birthday”, confidence:0.817751348019}, {|name|:”lit”, confidence:0.53784763813}}, categories:{{|name|:”others_”, score:0.01171875}}, |description|:{tags:{”table”, “indoor”, “cake”, “birthday”, “food”, “sitting”, “lit”, “plate”, “front”, “small”, “top”, “bowl”, “fruit”, “filled”, “chocolate”, “holding”, “man”, “woman”}, captions:{{|text|:“a birthday cake with lit candles”, confidence:0.8700279282}}}, requestId:”62b58a19-2d3a-4f9a-bd54-5aa4460d8385″}}

img_2187_resized.png
{{metadata:{width:3264, format:”Jpeg”, height:2448}, tags:{{name:”bottle”, confidence:0.999316096306}, {name:”table”, confidence:0.987863183022}, {name:”indoor”, confidence:0.968602716923}, {name:”wine”, confidence:0.928377449512}, {name:”alcohol”, confidence:0.846504926682}, {name:”food”, confidence:0.785823404789}, {name:”glass”, confidence:0.782037138939}, {name:”beverage”, confidence:0.776733756065}}, categories:{{name:”drink_”, score:0.90234375}}, description:{tags:{”bottle”, “table”, “indoor”, “wine”, “sitting”, “alcohol”, “food”, “glass”, “beverage”, “cup”, “banana”, “empty”, “top”, “sandwich”, “counter”, “beer”, “phone”, “orange”, “wooden”, “computer”, “laying”}, captions:{{text:“a bottle of wine”, confidence:0.858091829408}}}, requestId:”d2db3a05-fcb0-43bb-8191-89707904918a”}}

r0020016_resized.png
{{metadata:{width:1280, format:”Jpeg”, height:960}, tags:{{name:”sky”, confidence:0.999883055687}, {name:”outdoor”, confidence:0.997511506081}, {name:”person”, confidence:0.910886585712}, {name:”toy”, confidence:0.878364562988}}, categories:{{name:”others_”, score:0.00390625}, {name:”outdoor_”, score:0.01171875}}, description:{tags:{”outdoor”, “person”, “toy”, “thing”, “man”, “white”, “grass”, “standing”, “people”, “field”, “riding”, “jumping”, “doing”, “air”, “holding”, “group”, “trick”, “board”, “statue”, “dog”, “player”, “ramp”}, captions:{{text:“a statue of a man jumping in the air”, confidence:0.153422169506}}}, requestId:”ec5dc466-76c5-47df-b858-2e418ce891a8″}}

r0016164_resized.png
{{metadata:{width:2048, format:”Jpeg”, height:1536}, tags:{{name:”water”, confidence:0.999519586563}, {name:”animal”, confidence:0.993490874767}, {name:”outdoor”, confidence:0.992024421692}, {name:”aquatic mammal”, hint:”animal”, confidence:0.978799343109}, {name:”mammal”, hint:”animal”, confidence:0.914148926735}, {name:”ocean”, confidence:0.804374277592}, {name:”whale”, hint:”animal”, confidence:0.533892810345}, {name:”wave”, confidence:0.527211129665}, {name:”dolphin”, hint:”animal”, confidence:0.340564578772}}, categories:{{name:”outdoor_”, score:0.00390625}}, description:{tags:{”water”, “animal”, “outdoor”, “mammal”, “ocean”, “wave”, “laying”, “brown”, “riding”, “surfing”, “top”, “body”, “lying”, “board”, “large”, “beach”, “floating”, “swimming”, “standing”, “dog”, “young”, “white”, “man”}, captions:{{text:“a dog swimming in the ocean”, confidence:0.604725984086}}}, requestId:”161b6c0f-f3fd-4e06-b4be-d5baa9516e53″}}

AppleScript名:Analyze Image APIで画像認識
– Created 2017-05-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4625

set imgFilePath to POSIX path of (choose file of type {“public.jpeg”} with prompt “Select an Image to Analyze”)
set aRes to recogImage(imgFilePath) of me

on recogImage(imgFilePath)
  set reqURLStr to “https://api.projectoxford.ai/vision/v1.0/analyze”
  
set myAPIkey to retAPIkey() of me –API Key 1
  
  
set aRec to {visualFeatures:“Categories,Tags,Description”, details:“Celebrities”, language:“en”}
  
set bURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestPOSTAPIAndParseResultsWithImage(bURL, imgFilePath, myAPIkey) of me
  
set aRESTres to json of aRes
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
  
return (aRESTres as list)
end recogImage

–POST methodのREST APIを画像をアップロードしつつ呼ぶ
on callRestPOSTAPIAndParseResultsWithImage(aURL, imgFilePath, myAPIkey)
  –Get Image Contents from file
  
set imgPathStr to current application’s NSString’s stringWithString:imgFilePath
  
set imgData to current application’s NSData’s dataWithContentsOfFile:imgPathStr
  
set postBody to current application’s NSMutableData’s |data|()
  
postBody’s appendData:imgData
  
  
–Request
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setHTTPBody:postBody
  
aRequest’s setValue:“application/octet-stream” forHTTPHeaderField:“Content-Type”
  
aRequest’s setValue:myAPIkey forHTTPHeaderField:“Ocp-Apim-Subscription-Key”
  
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
  
–Parse Results
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
if dRes = missing value then
    set resCode to -1
    
set resHeaders to {}
  else
    set resCode to (dRes’s statusCode()) as integer
    
–Get Response Header
    
set resHeaders to (dRes’s allHeaderFields()) as record
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResultsWithImage

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString())
  
  
return aURL
end retURLwithParams

on retAPIkey()
  return “xXXxXXXXxXXxXXXxXXXXXXXXxXXXXXXx” –API Key 1
end retAPIkey

★Click Here to Open This Script 

2017/04/30 Computer Vision APIで文字認識(OCR)

MicrosoftのCognitive ServicesのREST APIのひとつ、Computer Vision APIを呼び出すAppleScriptです。

MicrosoftのAPIは割とシンプルでオンラインドキュメントも読みやすいため、書くのに3分ぐらいしかかかっていません(既存の別のAPI用のものをend pointとAPI keyとパラメータを書き換えただけ)。

Microsoft Cognitive Servicesのサイトで、開発者アカウント登録を行えば、本APIについては1か月に5,000回まで無料で使用できます(1分間に20回までという制限もあります)。アカウント登録を行い、OCR ServiceをEnableに設定して、本AppleScriptの末尾のretAPIKey()ハンドラ内にAPI Key 1を入力してください(API Keyを記入したScriptを決してそのまま第三者に配布しないでください。あくまでテストと確認用です)。

cognitive_key.png
▲ここの「Key 1」

Vision APIでは、対応画像ファイル形式はJPEG、PNG、GIF、BMP(!)。ファイル容量は4MB以下、50×50ピクセル以上の大きさである必要があります。

img_0320_resized.png

こんな「戦場の絆」のリプレイIDをゲームセンターで撮影して、このIDをもとにYouTubeのリプレイムービーのURLを検索することになるわけですが、このリプレイIDのOCRにComputer Vision APIを使ってみました。

この用途(ランダムに近い英数字の固定桁の組み合わせ、画面上のイメージの認識)については、かなり使える印象です。認識用に使用した画像は、iPhone 7で撮影して写真.appにiCloud経由で転送された写真をそのまま利用しており、ファイルサイズは2.6MB、画像の大きさは4032×3024ピクセルありました。このぐらいだと、認識のためにMicrosoftのクラウドにアップロードするのに少々待たされます。

{boundingBox:”929,1171,746,134″, text:”fy57nt2f“}
{boundingBox:”920,1438,770,135″, text:”3kt5y72v“}

この用途においては、とても誤認識が少ないと感じています。本来取り出したい目的のデータが日本語(ja)ではないので、言語を英数字(en)に指定して呼び出しています。

縦書きの日本語の印刷物の認識だと、もう少し結果が異なってくると思います。一応、この画面についても当初は日本語(ja)で認識していましたが、書体の問題もあり(ちょっと太すぎ?)日本語の文字については誤認識されまくっていました。

AppleScript名:Computer Vision APIで文字認識(OCR)
– Created 2017-04-30 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4622

set imgFilePath to POSIX path of (choose file of type {“public.image”} with prompt “Select an Image to OCR”)

set reqURLStr to “https://api.projectoxford.ai/vision/v1.0/ocr” –Microsoft Cognitive Services OCR endpoint
set myAPIkey to retAPIkey() of me –API Primary Key

–Request parameters
set aRec to {|language|:“en”, detectOrientation:“true”}
set bURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestPOSTAPIAndParseResultsWithImage(bURL, myAPIkey, imgFilePath) of me
set aRESTres to json of aRes

set aRESCode to responseCode of aRes
if aRESCode is not equal to 200 then return false
set aRESHeader to responseHeader of aRes

return ((regions of aRESTres) as list)
–>  {{language:”en”, regions:{{boundingBox:”728,366,1497,2042″, lines:{{boundingBox:”1655,366,186,28″, words:{{boundingBox:”1655,366,186,28″, text:”pea-a-r”}}}, {boundingBox:”1223,962,467,89″, words:{{boundingBox:”1223,962,467,89″, text:”UäLCOD”}}}, {boundingBox:”753,1134,922,171″, words:{{boundingBox:”753,1134,64,156″, text:”1″}, {boundingBox:”929,1171,746,134″, text:”fy57nt2f”}}}, {boundingBox:”1761,1167,458,64″, words:{{boundingBox:”1761,1175,80,53″, text:”20″}, {boundingBox:”1862,1169,169,62″, text:”17/0″}, {boundingBox:”2045,1167,174,60″, text:”4/26″}}}, {boundingBox:”2005,1231,215,52″, words:{{boundingBox:”2005,1231,215,52″, text:”17:55″}}}, {boundingBox:”742,1403,948,170″, words:{{boundingBox:”742,1403,86,160″, text:”2″}, {boundingBox:”920,1438,770,135″, text:”3kt5y72v”}}}, {boundingBox:”1765,1432,459,65″, words:{{boundingBox:”1765,1441,81,53″, text:”20″}, {boundingBox:”1866,1434,170,63″, text:”17/0″}, {boundingBox:”2050,1432,174,61″, text:”4/26″}}}, {boundingBox:”2010,1495,215,54″, words:{{boundingBox:”2010,1495,215,54″, text:”17:47″}}}, {boundingBox:”736,1672,88,176″, words:{{boundingBox:”736,1672,88,176″, text:”3″}}}, {boundingBox:”728,2240,96,168″, words:{{boundingBox:”728,2240,96,168″, text:”5″}}}}}, {boundingBox:”2817,1217,186,320″, lines:{{boundingBox:”2817,1217,175,53″, words:{{boundingBox:”2817,1217,175,53″, text:”6vs6″}}}, {boundingBox:”2826,1483,177,54″, words:{{boundingBox:”2826,1483,177,54″, text:”6vs6″}}}}}}, textAngle:0.0, orientation:”Up”}}

–POST methodのREST APIを画像をアップロードしつつ呼ぶ
on callRestPOSTAPIAndParseResultsWithImage(aURL, anAPIkey, imgFilePath)
  
  
–Get Image Contents from file
  
set imgPathStr to current application’s NSString’s stringWithString:imgFilePath
  
set imgData to current application’s NSData’s dataWithContentsOfFile:imgPathStr
  
set postBody to current application’s NSMutableData’s |data|()
  
postBody’s appendData:imgData
  
  
–Request
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setHTTPBody:postBody
  
aRequest’s setValue:“application/octet-stream” forHTTPHeaderField:“Content-Type”
  
aRequest’s setValue:anAPIkey forHTTPHeaderField:“Ocp-Apim-Subscription-Key”
  
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
  
–Parse Results
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to (dRes’s statusCode()) as integer
  
  
–Get Response Header
  
set resHeaders to (dRes’s allHeaderFields()) as record
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResultsWithImage

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) –as text
  
  
return aURL
end retURLwithParams

on retAPIkey()
  return “xXXXXxXxXxXXXXxxxXXxXXXxxXxxXxxx” –API Primary Key
end retAPIkey

★Click Here to Open This Script 

2017/04/27 テックサイト ごちゃまぜフィードを呼び出す

apitoreREST API「テックサイト ごちゃまぜフィード」を呼び出すAppleScriptです。

apitore側で、以下のテック系Blogを自動巡回して(30分に一度)更新情報を蓄積、新しい順番にソートしたものがREST API経由で提供されています。

巡回サイトは、ライフハッカー、メディア・パブ、スラッシュドット、ギズモード、ゆかしメディア、TechWave、TechDoll.jp、TechCrunch、Reuters、Google Japan Blog、GIGAZINE、Engadget、CNET、All About(オールアバウト) [新着記事]、男子ハック、Techable、ReadWrite Japan、MdN Design Interactive、WIRED.jp、アンドロイドアプリが見つかる!スマホ情報ならオクトバ、ITmedia 総合、ITmedia トップストーリー

となっています(記事執筆時)。

apitoreでは、この「テックサイト ごちゃまぜフィード」のほか、「旅行系」「ニュース」「ブログ 」「デザイン系」などの情報源のフィード系REST APIを提供しています。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得して、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

Webクローラーを自前で作って定期実行しなくても、Web API経由でクローリングした結果がもらえるというのは、とてもメリットが大きいと思います。AppleScriptではGUIアプリケーションの内部機能からOS内部のCocoa API、shell script、そしてWeb上のREST APIまで直接呼べるので、こんな「なんでもアリ」な環境はなかなかないでしょう。

AppleScript名:テックサイト ごちゃまぜフィード
– Created 2016-10-27 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4616

set allFeeds to {}

repeat with i from 1 to 100
  set {aRes, maxNum} to getAFeeds(i) of me
  
  
if aRes = false then exit repeat
  
set maxNum to maxNum as number
  
set allFeeds to allFeeds & aRes
  
if (maxNum div 10) i then
    exit repeat
  end if
end repeat

return allFeeds
–>  
(*
{{author:”Ittousai”, sourceTitle:”Engadget Japanese RSS Feed”, title:” 速報:サムスンGalaxy S8発表イベント UNPACKED 2017 (ライブ更新ページ)
“, link:”http://japanese.engadget.com/2017/03/29/galaxy-s8-unpacked-2017/”, pubDate:1.49079606E+12, description:”

“, sourceLink:”http://japanese.engadget.com/rss.xml”},
*)

on getAFeeds(aNum)
  set reqURLStr to “https://api.apitore.com/api/35/feeds/tech”
  
set accessToken to retAccessToken() —Access Token
  
set aRec to {access_token:accessToken, page:(aNum as string)}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestGETAPIAndParseResults(aURL) of me
  
  
set aRESCode to (responseCode of aRes) as integer
  
if aRESCode is not equal to 200 then return {false, false}
  
  
set aRESTres to (json of aRes) as record
  
  
set sentiRes to entries of aRESTres
  
set allNum to num of aRESTres
  
log allNum
  
  
return {sentiRes, allNum}
end getAFeeds

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return “XXXXxxxX-xxxx-XXXx-xxxX-XxxxXxXxxXXx” –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/03/31 簡単ツイート収集_search

Apitoreの「簡単ツイート収集」REST APIを呼び出して、指定キーワードによるTweet内容の検索を行うAppleScriptです。

実行前にApitoreにユーザー登録を行い(無料)、Web上でアクセストークンを取得。そのトークンをretAccessToken()内で返すように書いておく必要があります(掲載のリストのまま実行すると、エラーになります。かならずアクセストークンを取得してください)。

また、Twitterのサービスとの連携をオンにするために、Apitoreのマイページから「Twitterと連携する」をクリックし、ApitoreをTwitterとアプリ連携してください。

AppleScript名:簡単ツイート収集_search
– Created 2017-03-29 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4572

set aQuery to “#07Itakura”
set aResList to twitterSearchByQuery(aQuery) of me
–> {tweets:{{sentiment:missing value, favoritedCount:409, source:”Echofon“, retweetCount:392, userId:371790917, userName:”機動戦士ガンダム第07板倉小隊”, userScreenName:”07itakura”, text:”【お知らせ】本日放送した板倉小隊特番のアーカイブを明日26日の昼12時から板倉小隊のHPにて1ヵ月限定で配信決定!見逃してしまった方、もう一度見たいという方、ぜひお楽しみに! https://t.co/Q9q6VhiNGU #07itakura”, statusId:8.45606316664021E+17, sentimentScore:missing value, retweeted:false, favorited:false, createdAt:1.490443231E+12, userProfileImageURL:”http://pbs.twimg.com/profile_images/2578804231/hmzlhl1aaxaen8nf8i5b_normal.png”},….

on twitterSearchByQuery(aQuery)
  set reqURLStr to “https://api.apitore.com/api/23/twitter/search”
  
set accessToken to retAccessToken() —Access Token
  
set aRec to {access_token:accessToken, q:aQuery, sinceId:“-1″, maxId:“-1″, iter:“1″}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestGETAPIAndParseResults(aURL) of me
  
set aRESCode to (responseCode of aRes) as integer
  
set aRESTres to (json of aRes) as record
  
  
return aRESTres
end twitterSearchByQuery

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return “XXXXxxxX-xxxx-XXXx-xxxX-XxxxXxXxxXXx” –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/01/13 デコードしたQRコードのメールデータの各フィールドを取り出す v2.2

メールアドレス、Subject、Bodyの各フィールドが同梱されたQRコードをデコードした文字列を、docomo形式のフィールド形式と見立てて、各フィールドを取り出すAppleScriptです。

test_qr.png
–> “MATMSG:TO:hiyoko@piyocast.com;SUB:たいとる;BODY:ほんぶん;;”

こんな(↑)QRコードを読み取って、文字列にデコードできるわけですが、携帯電話用のQRコードはもともと、

名刺データ(氏名、住所など)交換、Webブックマーク(名前とURL)読み取り、メール文面生成などを目的として作られたため(フリー形式もありますけれども)、単にデータをデコードしただけではなく、これらのデータ形式であるかどうかのチェックが必要です。

これらのデータ形式だった場合には、それぞれフィールド値を取り出す必要があるわけですが、ここではメール文面の生成のみ対象にしています。

さて、各フィールドを解釈しようとしても、日本には主に3つの携帯電話キャリアがあり(docomo、au、softbank)、それぞれ微妙にこのフィールド定義形式が異なります。

調査したところ、docomo形式でないと読み取れないiOSアプリがあったため、docomo形式をデコードすることにします(AppStoreでも5本ぐらいのiOSアプリしかフィールド検出できませんでした)。海外産のiOSアプリでQRコードのこれらのデータ形式に対応しているケースではdocomo形式にのみ対応しているようでした(それでもあまたの未対応iOSアプリの1億倍えらい)。

先日、国内外のマニアさんによって寄ってたかって改良されまくった「&と=で区切られたテキストをrecordに(NSScanner版)」をもとに若干の手直しを行い、このためのサブルーチンに作り変えてみました。

Cocoaを呼び出すことで、AppleScript単体で(他のアプリケーションの力を借りずに)QRコードの作成や認識はできていますが、実際には携帯電話側でQRコードを読み取り、その内容をメールでMac側に転送し、Mac側で評価するような運用を想定しています。本Scriptはそのための「準備段階」のものです。

だいたいは、スマホでQRコードをスキャンして、その情報をメールで送ってくるので、Mac側で処理することを考えると、本文にデータが入ってくることだけを見ていればいいはずです。なので、スマホから来たデータ処理のためにはこんなたいそうなルーチンは必要ありません。

一応、Mac側でQRコードをデコードしたあとの「解釈」を行えるようにしておくことは無駄にはならないでしょう。

AppleScript名:デコードしたQRコードのメールデータの各フィールドを取り出す v2.2
– Created 2016-12-12 by Shane Stanley
– Modified 2016-12-14 by edama2
– Modified 2017-01-12 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4387

set aStr to “こんにちは、ぴよまるです

MATMSG:TO:hiyoko@piyocast.com;SUB:たいとる;BODY:ほんぶん;;

Takaaki Naganoya
XXX-XXXX-XXXX

iPhoneから送信”

set aDict to (parseStrByParamlabelAndTail(aStr, “MATMSG:”, “:”, “;”) of me)

set eMailAddrs to (aDict’s valueForKey:“TO”) as string
–>  ”hiyoko@piyocast.com”
set aSubject to (aDict’s valueForKey:“SUB”) as string
–>  ”たいとる”
set aBody to (aDict’s valueForKey:“BODY”) as string
–>  ”ほんぶん”

on parseStrByParamlabelAndTail(aParamStr, aDataHeader, aParamLabel, aParamTail)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
  
—Skip over the data header
  
set {theResult, theKey} to theScanner’s scanUpToString:aDataHeader intoString:(reference)
  
if theResult as boolean = false then return false –Error: Data header is not present
  
  
theScanner’s scanString:aDataHeader intoString:(missing value)
  
  
repeat until (theScanner’s isAtEnd as boolean)
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:aParamLabel intoString:(reference)
    
    
– skip over separator
    
theScanner’s scanString:aParamLabel intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:aParamTail intoString:(reference)
    
if theValue is missing value then set theValue to “”
    
    
– skip over separator
    
theScanner’s scanString:aParamTail intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
  end repeat
  
  
return aDict
end parseStrByParamlabelAndTail

★Click Here to Open This Script 

2017/01/03 NSMapTableのじっけん

どうしてもNSMapTableを作って呼び出す必要のある部品があって、仕方なくNSMapTableについて調査。「NSMutableDictionaryとほぼ同じだがメモリー上の管理方式が異なる」という以上の説明がなく、なかなか強敵です。

しかも、NSDictionary/NSMutableDictionaryから変換するメソッドなどもないため、なかなか作るのもめんどくさいと来ています。

NSMapTable自体を操作するメソッドも少ないので、AppleScript側にとってあまりメリットはなさそうですが、とりあえずNSDictionary/NSMutableDictionaryとの間で相互変換できるようにしてみました。

AppleScript名:NSMapTableのじっけん
– Created 2017-01-02 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

–AppleScriptではStrongでもWeakでも関係ない???
set aMapTable to current application’s NSMapTable’s mapTableWithKeyOptions:(current application’s NSMapTableStrongMemory) valueOptions:(current application’s NSMapTableStrongMemory)

–set aMapTable to current application’s NSMapTable’s mapTableWithKeyOptions:(current application’s NSMapTableWeakMemory) valueOptions:(current application’s NSMapTableWeakMemory)

aMapTable’s setObject:“1″ forKey:“foo1″
aMapTable’s setObject:“2″ forKey:“foo2″
aMapTable’s |count|()
–> 2

aMapTable’s objectEnumerator()’s allObjects() as list –objects
–>  {”2″, “1″}

aMapTable’s keyEnumerator()’s allObjects() as list –keys
–>  {”foo2″, “foo1″}

aMapTable’s dictionaryRepresentation() as record –NSMapTable to NSDictionary to record
–>  {foo:”1″, foo2:”2″}

–以下はあまり関係ない?
–aMapTable’s keyPointerFunctions()
–>  (NSConcretePointerFunctions)

–aMapTable’s valuePointerFunctions()
–>  (NSConcretePointerFunctions)

★Click Here to Open This Script 

record –> NSMutableDictionary –> NSMapTable と変換するサブルーチンも作ってみました。

AppleScript名:NSMapTableのじっけん(NSDictionary to NSMapTable)
– Created 2017-01-02 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aRec to {foo1:1, foo2:2, foo3:3, foo4:4, foo5:5}
set aDict to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
set aMap to convertDictToMap(aDict) of me
(*
–>  (NSConcreteMapTable) NSMapTable {
[1] foo2 -> 2
[5] foo4 -> 4
[7] foo5 -> 5
[11] foo3 -> 3
[14] foo1 -> 1
}
*)


–Verify
set bDict to aMap’s dictionaryRepresentation() as record
–>  {foo1:1, foo4:4, foo2:2, foo5:5, foo3:3}

on convertDictToMap(aDict)
  set aMapTable to current application’s NSMapTable’s mapTableWithKeyOptions:(current application’s NSMapTableStrongMemory) valueOptions:(current application’s NSMapTableStrongMemory)
  
  set keyList to aDict’s allKeys() as list
  
set valList to aDict’s allValues() as list
  
set aLen to length of keyList
  
  repeat with i from 1 to aLen
    set aKey to contents of item i of keyList
    
set aVal to contents of item i of valList
    (
aMapTable’s setObject:aVal forKey:aKey)
  end repeat
  
  return aMapTable
end convertDictToMap

★Click Here to Open This Script 

recordから直接NSMapTableに変換するものも作っておきました。

AppleScript名:NSMapTableのじっけん(record to NSMapTable)
– Created 2017-01-02 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aRec to {foo1:1, foo2:2, foo3:3, foo4:4, foo5:5}
set aMap to convertRecToMap(aRec) of me
(*
–>  (NSConcreteMapTable) NSMapTable {
[1] foo2 -> 2
[5] foo4 -> 4
[7] foo5 -> 5
[11] foo3 -> 3
[14] foo1 -> 1
}
*)

–Verify
set bDict to aMap’s dictionaryRepresentation() as record
–>  {foo1:1, foo4:4, foo2:2, foo5:5, foo3:3}

on convertRecToMap(aRec as record)
  set aDict to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aMapTable to current application’s NSMapTable’s mapTableWithKeyOptions:(current application’s NSMapTableStrongMemory) valueOptions:(current application’s NSMapTableStrongMemory)
  
  
set keyList to aDict’s allKeys() as list
  
set valList to aDict’s allValues() as list
  
set aLen to length of keyList
  
  
repeat with i from 1 to aLen
    set aKey to contents of item i of keyList
    
set aVal to contents of item i of valList
    (
aMapTable’s setObject:aVal forKey:aKey)
  end repeat
  
  
return aMapTable
end convertRecToMap

★Click Here to Open This Script 

2016/12/14 &と=で区切られたテキストをrecordに 改

「aParam=1234&bParam=2345」のように、URLのパラメータ部のような文字列を「&」と「=」で区切ってrecordとして出力するAppleScriptです。

「&と=で区切られたテキストをrecordに(NSScanner版)」をedama2さんがさらに改良したものです。

「先日掲載されたNSScannerのスクリプト、自分でも試してみてとても便利だったんですが、値なしの時にエラーになるので修正してみました。

あとリファレンス本に「repeat until」はあんまり使わないと書いてあったので使ってみました。(edama2さん)」

なるほど。たしかにラベルだけで値が存在しないケースに対応できるとよさそうです。

AppleScript名:&と=で区切られたテキストをrecordに 改
– Created 2016-12-12 by Shane Stanley
– Modified 2016-12-14 by edama2
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4358

set aParamStr to "access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399&name="
set aDict to (parseStrByAmpAndEqual(aParamStr) of me)
–>  {expires:"86399", |name|:"", access_token:"XXxxx(XXxXXXXXxxXxxXXx))"}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
  
repeat until (theScanner’s isAtEnd as boolean)
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:"=" intoString:(reference)
    
    
– skip over separator
    
theScanner’s scanString:"=" intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:"&" intoString:(reference)
    
if theValue is missing value then set theValue to "" –>追加
    
    
– skip over separator
    
theScanner’s scanString:"&" intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
  end repeat
  
  
return aDict as record
end parseStrByAmpAndEqual

★Click Here to Open This Script 

2016/12/12 &と=で区切られたテキストをrecordに(NSScanner版)

「aParam=1234&bParam=2345」のように、URLのパラメータ部のような文字列を「&」と「=」で区切ってrecordとして出力するAppleScriptです。

Shane Stanleyから「NSScannerを使った実装例」を教えてもらいました。「高速ではないけど、NSSCannerは便利なクラスなので慣れておいたほうがいいよ」という実例です。

Cocoaには、AppleScriptの処理系にはまったく存在していない強力な機能があり、使うととても便利で、プログラムを短く書くことができ、圧倒的に高速処理が可能です。

 リスト項目のユニーク化:NSSet
 リスト項目のユニーク化と出現回数カウント:NSCountedSet
 リスト項目のユニーク化とソート:NSMutableIndexSet
 リスト項目のしぼりこみ:NSPredicate

など、もはやプログラムを書く上で「ないと困る!」というレベルの道具になっています。ファイルパスの加工が強力なNSStringもないと困るレベルのものです。

NSScannerについて、Shaneのプログラムをそのまま掲載するとこんな感じですが、これは「巨大なプログラムを書き慣れた人が書く」タイプのプログラムであり、個別に理解を深めようとすると・・・つまり、「教材」として提示する際には若干の清書が必要になります。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner Orig)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
repeat
    set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
aDict’s setObject:theValue forKey:theKey
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

自分が動作内容を確認するためには、こんな感じに

(1)意味を取りやすいように空行を追加
(2)プログラムの動きを理解しやすいように、さまざまな変数のログ表示を行う
(3)プログラムのInputとOutputがわかりやすいように、サンプルデータの処理結果を掲載

のように書き換えます。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner 2)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record
–>  {expires:”86399″, access_token:”XXxxx(XXxXXXXXxxXxxXXx))”}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
log (theScanner’s scanLocation())
  
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
repeat
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
log (theScanner’s scanLocation())
    
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
log (theScanner’s scanLocation())
    
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
    
log (theScanner’s scanLocation())
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

そして、最終的に確認用のコードを削除して(クリーニングして)、こういうリストを掲載しています。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner 1)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record
–>  {expires:”86399″, access_token:”XXxxx(XXxXXXXXxxXxxXXx))”}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
  
repeat
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

2016/12/05 IPIFYで自分のグローバルIPアドレスを取得

ipfy.orgを呼び出して、グローバルIPアドレスを取得するAppleScriptです。

以前に掲載した「自分の(インターネットアクセス時の)IPアドレスを調べる」と同じ働きをするものです。

同じ結果が得られることを確認していますが、こちらの方が高速にアドレスを取得できています。

AppleScript名:IPIFYで自分のグローバルIPアドレスを取得
– Created 2016-12-04 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4343

set reqURLStr to “https://api.ipify.org”
set aRec to {|format|:“json”}
set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestGETAPIAndParseResults(aURL) of me
set aRESCode to (responseCode of aRes) as integer
if aRESCode is not equal to 200 then error “Server Error”

set aRESHeader to responseHeader of aRes
set aRESTres to |ip| of ((json of aRes) as record)

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

★Click Here to Open This Script 

2016/11/30 Livedoorお天気WebサービスAPIで指定地点の天気予報を取得する

livedoorのお天気WebサービスAPIを呼び出して、指定地点コードで指定した地点の天気予報を取得するAppleScriptです。

本サービスを呼び出すのに、開発者登録を行う必要はありません。

例として東京都の東京地方のコード(130010)を指定しています。

現在の緯度・軽度情報を取得して、その情報から住所コードを取得する(逆住所ジオコーディング)と、このようなサービスを利用して「現在位置の天気予報を取得する」という処理ができます。

AppleScript名:Livedoorお天気WebサービスAPIで指定地点の天気予報を取得する
– Created 2016-10-29 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
–http://weather.livedoor.com/weather_hacks/webservice
–http://piyocast.com/as/archives/4337

set reqURLStr to “http://weather.livedoor.com/forecast/webservice/json/v1″

set aRec to {city:“130010″}
set aURL to retURLwithParams(reqURLStr, aRec) of me
set aRes to callRestGETAPIAndParseResults(aURL) of me

set aRESCode to responseCode of aRes
if aRESCode is not equal to 200 then error “Server Error”

set aRESHeader to responseHeader of aRes

set aRESTres to (json of aRes) as record
return aRESTres
(*
{location:{prefecture:”東京都”, city:”東京”, area:”関東”}, publicTime:”2016-11-30T17:00:00+0900″, title:”東京都 東京 の天気”, copyright:{provider:{{link:”http://tenki.jp/”, |name|:”日本気象協会”}}, title:”(C) LINE Corporation”, image:{height:26, title:”livedoor 天気情報”, width:118, |url|:”http://weather.livedoor.com/img/cmn/livedoor.gif”, link:”http://weather.livedoor.com/”}, link:”http://weather.livedoor.com/”}, link:”http://weather.livedoor.com/area/forecast/130010″, forecasts:{{|date|:”2016-11-30″, dateLabel:”今日”, temperature:{min:missing value, max:missing value}, image:{title:”曇り”, width:50, |url|:”http://weather.livedoor.com/img/icon/8.gif”, height:31}, telop:”曇り”}, {|date|:”2016-12-01″, dateLabel:”明日”, temperature:{min:{celsius:”6″, fahrenheit:”42.8″}, max:{celsius:”14″, fahrenheit:”57.2″}}, image:{title:”曇時々雨”, width:50, |url|:”http://weather.livedoor.com/img/icon/10.gif”, height:31}, telop:”曇時々雨”}, {|date|:”2016-12-02″, dateLabel:”明後日”, temperature:{min:missing value, max:missing value}, image:{title:”晴時々曇”, width:50, |url|:”http://weather.livedoor.com/img/icon/2.gif”, height:31}, telop:”晴時々曇”}}, |description|:{|text|:” 高気圧が三陸沖を東へ移動しています。一方、黄海付近は気圧の谷となっ
ています。

【関東甲信地方】
関東甲信地方は、気圧の谷や湿った空気の影響で曇りとなっています。

30日は、曇りで、気圧の谷や湿った空気の影響で、夜には雨の降る所が
あるでしょう。

12月1日は、気圧の谷の影響により、曇りで、朝にかけて雨の所が多い
見込みです。伊豆諸島では朝にかけて雷を伴う所があるでしょう。

関東近海では、30日から12月1日にかけて波が高いでしょう。船舶は
高波に注意してください。

【東京地方】
30日は、曇りで、夜遅くは雨の降る所があるでしょう。
12月1日は、曇りで、明け方まで雨となる見込みです。”, publicTime:”2016-11-30T16:34:00+0900″}, pinpointLocations:{{link:”http://weather.livedoor.com/area/forecast/1310100″, |name|:”千代田区”}, {link:”http://weather.livedoor.com/area/forecast/1310200″, |name|:”中央区”}, {link:”http://weather.livedoor.com/area/forecast/1310300″, |name|:”港区”}, {link:”http://weather.livedoor.com/area/forecast/1310400″, |name|:”新宿区”}, {link:”http://weather.livedoor.com/area/forecast/1310500″, |name|:”文京区”}, {link:”http://weather.livedoor.com/area/forecast/1310600″, |name|:”台東区”}, {link:”http://weather.livedoor.com/area/forecast/1310700″, |name|:”墨田区”}, {link:”http://weather.livedoor.com/area/forecast/1310800″, |name|:”江東区”}, {link:”http://weather.livedoor.com/area/forecast/1310900″, |name|:”品川区”}, {link:”http://weather.livedoor.com/area/forecast/1311000″, |name|:”目黒区”}, {link:”http://weather.livedoor.com/area/forecast/1311100″, |name|:”大田区”}, {link:”http://weather.livedoor.com/area/forecast/1311200″, |name|:”世田谷区”}, {link:”http://weather.livedoor.com/area/forecast/1311300″, |name|:”渋谷区”}, {link:”http://weather.livedoor.com/area/forecast/1311400″, |name|:”中野区”}, {link:”http://weather.livedoor.com/area/forecast/1311500″, |name|:”杉並区”}, {link:”http://weather.livedoor.com/area/forecast/1311600″, |name|:”豊島区”}, {link:”http://weather.livedoor.com/area/forecast/1311700″, |name|:”北区”}, {link:”http://weather.livedoor.com/area/forecast/1311800″, |name|:”荒川区”}, {link:”http://weather.livedoor.com/area/forecast/1311900″, |name|:”板橋区”}, {link:”http://weather.livedoor.com/area/forecast/1312000″, |name|:”練馬区”}, {link:”http://weather.livedoor.com/area/forecast/1312100″, |name|:”足立区”}, {link:”http://weather.livedoor.com/area/forecast/1312200″, |name|:”葛飾区”}, {link:”http://weather.livedoor.com/area/forecast/1312300″, |name|:”江戸川区”}, {link:”http://weather.livedoor.com/area/forecast/1320100″, |name|:”八王子市”}, {link:”http://weather.livedoor.com/area/forecast/1320200″, |name|:”立川市”}, {link:”http://weather.livedoor.com/area/forecast/1320300″, |name|:”武蔵野市”}, {link:”http://weather.livedoor.com/area/forecast/1320400″, |name|:”三鷹市”}, {link:”http://weather.livedoor.com/area/forecast/1320500″, |name|:”青梅市”}, {link:”http://weather.livedoor.com/area/forecast/1320600″, |name|:”府中市”}, {link:”http://weather.livedoor.com/area/forecast/1320700″, |name|:”昭島市”}, {link:”http://weather.livedoor.com/area/forecast/1320800″, |name|:”調布市”}, {link:”http://weather.livedoor.com/area/forecast/1320900″, |name|:”町田市”}, {link:”http://weather.livedoor.com/area/forecast/1321000″, |name|:”小金井市”}, {link:”http://weather.livedoor.com/area/forecast/1321100″, |name|:”小平市”}, {link:”http://weather.livedoor.com/area/forecast/1321200″, |name|:”日野市”}, {link:”http://weather.livedoor.com/area/forecast/1321300″, |name|:”東村山市”}, {link:”http://weather.livedoor.com/area/forecast/1321400″, |name|:”国分寺市”}, {link:”http://weather.livedoor.com/area/forecast/1321500″, |name|:”国立市”}, {link:”http://weather.livedoor.com/area/forecast/1321800″, |name|:”福生市”}, {link:”http://weather.livedoor.com/area/forecast/1321900″, |name|:”狛江市”}, {link:”http://weather.livedoor.com/area/forecast/1322000″, |name|:”東大和市”}, {link:”http://weather.livedoor.com/area/forecast/1322100″, |name|:”清瀬市”}, {link:”http://weather.livedoor.com/area/forecast/1322200″, |name|:”東久留米市”}, {link:”http://weather.livedoor.com/area/forecast/1322300″, |name|:”武蔵村山市”}, {link:”http://weather.livedoor.com/area/forecast/1322400″, |name|:”多摩市”}, {link:”http://weather.livedoor.com/area/forecast/1322500″, |name|:”稲城市”}, {link:”http://weather.livedoor.com/area/forecast/1322700″, |name|:”羽村市”}, {link:”http://weather.livedoor.com/area/forecast/1322800″, |name|:”あきる野市”}, {link:”http://weather.livedoor.com/area/forecast/1322900″, |name|:”西東京市”}, {link:”http://weather.livedoor.com/area/forecast/1330300″, |name|:”瑞穂町”}, {link:”http://weather.livedoor.com/area/forecast/1330500″, |name|:”日の出町”}, {link:”http://weather.livedoor.com/area/forecast/1330700″, |name|:”檜原村”}, {link:”http://weather.livedoor.com/area/forecast/1330800″, |name|:”奥多摩町”}}}
*)

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on urlencodeStr(aStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set aString to (aString’s stringByAddingPercentEncodingWithAllowedCharacters:(current application’s NSCharacterSet’s URLQueryAllowedCharacterSet())) as text
  
return aString
end urlencodeStr

★Click Here to Open This Script 

2016/11/30 Yahoo! 形態素解析APIで日本語テキストを解釈

Yahoo!の形態素解析APIで、日本語テキストを形態素解析するAppleScriptです。

Yahoo!に開発者登録(無料)して、アプリケーションIDを取得し、リスト中のretAccessKey()ハンドラにアプリケーションIDを記入すると実行可能です。

単語ごとに「品詞」「よみがな」などを取得できます。辞書が充実しているためか、自分の名前も正しく単語として認識されました。

形態素解析エンジンはローカルに置いて、辞書をカスタマイズするべきだと思っていますが、Yahoo!のAPI(が備えている辞書)だとそれなりに使える感じがします。

Yahoo!のテキスト解析系APIはひととおり試してみましたが、

 校正支援API:漢字の誤変換は指摘してくれるが、助詞の間違いなどは指摘してくれない
 キーフレーズ抽出API:使えるかどうか評価が難しい
 かな漢字変換API:呼んで使えるが、使い道が難しい

この形態素解析APIが一番実用度が高そうな感じがします。

AppleScript名:Yahoo! 形態素解析APIで日本語テキストを解釈
– Created 2016-11-25 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”

–http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html

–日本語形態素解析Web APIは、24時間以内で1つのアプリケーションIDにつき50000件のリクエストが上限となっています。また、1リクエストの最大サイズを100KBに制限 しています。

property dictStack : missing value – stack to hold array of dictionaries
property textInProgress : “” – string to collect text as it is found
property anError : missing value – if we get an error, store it here

set japaneseText to “私の名前は長野谷です。”

set reqURLStr to “http://jlp.yahooapis.jp/MAService/V1/parse”

set aKey to retAccessKey() of me

set aRec to {|key|:aKey, sentence:japaneseText, results:“ma”, page:“1″, output:“xml”, appid:aKey}
set aURL to retURLwithParams(reqURLStr, aRec) of me
set aRes to callRestGETAPIAndParseXMLResults(aURL) of me

set aRESCode to responseCode of aRes
if aRESCode is not equal to 200 then return false

set aRESHeader to responseHeader of aRes
set aXMLres to (xml of aRes)

set parsedList to (aXMLres’s valueForKeyPath:“ResultSet.ma_result.word_list.word.surface.contents”) as list
–>  {”私”, “の”, “名前”, “は”, “長野谷”, “です”, “。”}

set yomiganaList to (aXMLres’s valueForKeyPath:“ResultSet.ma_result.word_list.word.reading.contents”) as list
–>  {”わたし”, “の”, “なまえ”, “は”, “ながのや”, “です”, “。”}

set kindList to (aXMLres’s valueForKeyPath:“ResultSet.ma_result.word_list.word.pos.contents”) as list
–>  {”名詞”, “助詞”, “名詞”, “助詞”, “名詞”, “助動詞”, “特殊”}

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseXMLResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set aXmlRec to my makeR