MacのMail.appで受信したメールが『安全』かどうかを判断するプログラムを作ってみました。
全てを弾く事はなかなか困難ではありますが、判断材料として使えるようにしました。
プログラミングエディタ:スクリプトエディタ
開発環境:MacOS 12.3 Monterey
スクリプトエディタv2.11(227)
AppleScript 2.8
使用:NSWindow, NSView, NSTableView, NSScrollView, NSTextField, NSTextView, NSButton,
NSProgressIndicator, NSFont, NSColor, NSMakeRect, NSMakeSize, などです。
Mail.appの『全て』または『選択部分』または『未読部分』のメールを表示し、それが危険か注意が必要かを表示します。また、メッセージ項目をダブルクリックする事で詳細を表示します。
Mail.appが起動してることが必要です(プログラムを実行すると自動で立ち上がります)
すべてのソース
use AppleScript version "2.7" use scripting additions use framework "Foundation" use framework "AppKit" global myApp global theWindow, mainView global closeFlg property theDataSource : {} global scrollView1, tableView1, sourceList global button1, button2, button3, view1, textView1, textField1, progressIndicator1 global mailList, mailNo on run launch application "Mail" -- my performSelectorOnMainThread:"mainMakeObject:" withObject:(missing value) waitUntilDone:true -- log "End" end run on mainMakeObject:inputObj set myApp to current application set closeFlg to false set aTitle to "Suspicious email" set aRect to {300, 150, 1000, 640} set moveCenter to true set theWindow to my makeNewWindow(aTitle, aRect, moveCenter) my makeObject() theWindow's setAlphaValue:1.0 -- repeat if closeFlg then my closeWin:theWindow exit repeat end if delay 0.2 end repeat end mainMakeObject: on makeNewWindow(aTitle, aRect, moveCenter) set {windowX, windowY, windowW, windowH} to aRect set theRect to myApp's NSMakeRect(windowX, windowY, windowW, windowH) set aScreen to myApp's class "NSScreen"'s mainScreen() set aStyle to (myApp's NSWindowStyleMaskTitled as integer) set aStyle to aStyle + (myApp's NSWindowStyleMaskClosable as integer) set aStyle to aStyle + (myApp's NSWindowStyleMaskMiniaturizable as integer) set aStyle to aStyle + (myApp's NSWindowStyleMaskResizable as integer) set aBacking to myApp's NSBackingStoreBuffered set aWindow to myApp's class "NSWindow"'s alloc()'s initWithContentRect:theRect styleMask:aStyle backing:aBacking defer:false screen:aScreen tell aWindow setAlphaValue_(0.0) setDelegate_(me) setTitle_(aTitle) setMinSize_(myApp's NSMakeSize(640, 420)) setMaxSize_(myApp's NSMakeSize(10000, 10000)) setBackgroundColor_(myApp's class "NSColor"'s colorWithCalibratedRed:0.95 green:0.95 blue:0.95 alpha:1.0) setDisplaysWhenScreenProfileChanges_(true) setReleasedWhenClosed_(true) makeKeyAndOrderFront_(me) end tell -- if moveCenter then aWindow's |center|() set theScreen to myApp's class "NSScreen"'s screens() set {windowCenterX, winsowCenterY} to {windowX + (windowW / 2), windowY + (windowH / 2)} repeat with screenNo from 1 to (count of theScreen) set {{screenX1, screenY1}, {screenWidth, screenHeight}} to (item screenNo of theScreen)'s frame() set {screenX2, screenY2} to {screenX1 + screenWidth, screenY1 + screenHeight} if (windowCenterX > screenX1) and (windowCenterX < screenX2) and (winsowCenterY > screenY1) and (winsowCenterY < screenY2) then set originX to screenX1 + (screenWidth / 2) - (windowW / 2) set originY to screenY1 + (screenHeight / 2) - (windowH / 2) (aWindow's setFrameOrigin:(myApp's NSMakePoint(originX, originY))) exit repeat end if end repeat end if -- return aWindow end makeNewWindow (* 各オブジェクトの配置 *) on makeObject() set mainView to theWindow's contentView set theWindowFrame to mainView's frame() set {{windowX, windowY}, {windowWidth, windowHeight}} to theWindowFrame -- set minXMargin to (myApp's NSViewMinXMargin) as integer -- 左の余白を柔軟に set maxXMargin to (myApp's NSViewMaxXMargin) as integer -- 右の余白を柔軟に set minYMargin to (myApp's NSViewMinYMargin) as integer -- 下の余白を柔軟に set maxYMargin to (myApp's NSViewMaxYMargin) as integer -- 上の余白を柔軟に set widthSizable to (myApp's NSViewWidthSizable) as integer -- 横方向の幅を柔軟に set heightSizable to (myApp's NSViewHeightSizable) as integer -- 縦方向の幅を柔軟に set fixedTopAndRight to minYMargin + minXMargin -- 右上を固定 & 天地左右の幅を固定 set freeSizeWidthHeight to widthSizable + heightSizable -- 天地左右の余白固定 & 天地左右の幅を自由に set fixedButtomAndRight to maxYMargin + minXMargin -- 下と右を固定・幅固定 set freeTopBottomAndLeftRight to minXMargin + maxXMargin + minYMargin + maxYMargin --サイズはそのままで位置を自由に -- try -- 新しくオブジェクトを配置する部分 -------------------- (* TableView *) set {x, y, w, h} to {3, 3, windowWidth - 83, windowHeight - 6} set sourceList to {} set theDataSource to myApp's class "NSMutableArray"'s alloc()'s init() theDataSource's addObjectsFromArray:sourceList set aColumn1 to myApp's class "NSTableColumn"'s alloc()'s initWithIdentifier:"data1" tell aColumn1 headerCell()'s setTitle:"判定" setEditable_(false) setWidth_(50) end tell set aColumn2 to myApp's class "NSTableColumn"'s alloc()'s initWithIdentifier:"data2" tell aColumn2 headerCell()'s setTitle:"送信者" setEditable_(false) setWidth_(220) end tell set aColumn3 to myApp's class "NSTableColumn"'s alloc()'s initWithIdentifier:"data3" tell aColumn3 headerCell()'s setTitle:"タイトル" setEditable_(false) setWidth_(400) end tell set aColumn4 to myApp's class "NSTableColumn"'s alloc()'s initWithIdentifier:"data4" tell aColumn4 headerCell()'s setTitle:"受信日" setEditable_(false) setWidth_(200) end tell set tableView1 to myApp's class "NSTableView"'s alloc()'s initWithFrame:(myApp's NSMakeRect(0, 0, w, h)) tell tableView1 setDelegate_(me) addTableColumn_(aColumn1) addTableColumn_(aColumn2) addTableColumn_(aColumn3) addTableColumn_(aColumn4) setDataSource_(me) setUsesAlternatingRowBackgroundColors_(true) setDoubleAction_("tableAction:") end tell set scrollView1 to myApp's class "NSScrollView"'s alloc()'s initWithFrame:(myApp's NSMakeRect(x, y, w, h)) scrollView1's setDocumentView:tableView1 theWindow's contentView()'s addSubview:scrollView1 tell scrollView1 setHasHorizontalScroller_(true) setHasVerticalScroller_(true) setAutohidesScrollers_(true) setAutoresizingMask_(freeSizeWidthHeight) end tell tableView1's reloadData() (* TextField *) set theRect to myApp's NSMakeRect(windowWidth - 73, windowHeight - 30, 75, 24) set aTextField to myApp's class "NSTextField"'s alloc()'s initWithFrame:theRect tell aTextField setDelegate_(me) setEditable_(false) setBordered_(false) setStringValue_("項目の取得") setDrawsBackground_(false) setAutoresizingMask_(fixedTopAndRight) end tell theWindow's contentView()'s addSubview:aTextField (* Button 1~3 *) set theRect to myApp's NSMakeRect(windowWidth - 80, windowHeight - 50, 80, 24) set button1 to myApp's class "NSButton"'s alloc()'s initWithFrame:theRect tell button1 setBezelStyle_(myApp's NSBezelStyleRounded) setTitle_("全て") setTarget_(me) setAction_("buttonAction1:") setAutoresizingMask_(fixedTopAndRight) end tell mainView's addSubview:button1 -- set theRect to myApp's NSMakeRect(windowWidth - 80, windowHeight - 75, 80, 24) set button2 to myApp's class "NSButton"'s alloc()'s initWithFrame:theRect tell button2 setBezelStyle_(myApp's NSBezelStyleRounded) setTitle_("選択部分") setTarget_(me) setAction_("buttonAction2:") setAutoresizingMask_(fixedTopAndRight) end tell mainView's addSubview:button2 -- set theRect to myApp's NSMakeRect(windowWidth - 80, windowHeight - 100, 80, 24) set button3 to myApp's class "NSButton"'s alloc()'s initWithFrame:theRect tell button3 setBezelStyle_(myApp's NSBezelStyleRounded) setTitle_("未読部分") setTarget_(me) setAction_("buttonAction3:") setAutoresizingMask_(fixedTopAndRight) end tell mainView's addSubview:button3 (* ProgressIndicator *) set theRect to myApp's NSMakeRect((windowWidth / 2) - 85, (windowHeight / 2) - 25, 50, 50) set progressIndicator1 to myApp's class "NSProgressIndicator"'s alloc()'s initWithFrame:theRect tell progressIndicator1 setDoubleValue_(1.0) setMinValue_(0.0) setMaxValue_(1.0) setStyle_(myApp's NSProgressIndicatorStyleSpinning) setIndeterminate_(false) setAlphaValue_(0.0) setAutoresizingMask_(freeTopBottomAndLeftRight) end tell mainView's addSubview:progressIndicator1 (* TextField - message *) set theRect to myApp's NSMakeRect((windowWidth / 2) - 160, (windowHeight / 2) - 70, 200, 25) set textField1 to myApp's class "NSTextField"'s alloc()'s initWithFrame:theRect tell textField1 setDelegate_(me) setEditable_(false) setBordered_(true) setStringValue_("Controlキーで中止できます") setAlignment_(myApp's NSTextAlignmentCenter) setDrawsBackground_(true) setBackgroundColor_(myApp's class "NSColor"'s yellowColor()) setAlphaValue_(0.0) setAutoresizingMask_(freeTopBottomAndLeftRight) end tell theWindow's contentView()'s addSubview:textField1 (* SubView *) set theRect to myApp's NSMakeRect(0, 0, windowWidth, windowHeight) set view1 to myApp's class "NSView"'s alloc()'s initWithFrame:theRect tell view1 setAlphaValue_(0.9) view1's setHidden:true setAutoresizingMask_(freeSizeWidthHeight) end tell mainView's addSubview:view1 (* view - shadow *) set theRect to myApp's NSMakeRect(48, 45, windowWidth - 173, windowHeight - 93) set view2 to myApp's class "NSView"'s alloc()'s initWithFrame:theRect tell view2 setAlphaValue_(0.7) setBackgroundColor_(myApp's class "NSColor"'s whiteColor()) setAutoresizingMask_(freeSizeWidthHeight) end tell view1's addSubview:view2 (* TextView *) set {x, y, w, h} to {50, 50, windowWidth - 180, windowHeight - 100} set theRect to myApp's NSMakeRect(x, y, w, h) set scrollView1 to myApp's class "NSScrollView"'s alloc()'s initWithFrame:theRect tell scrollView1 setBorderType_(myApp's NSLineBorder) setHasVerticalScroller_(true) setHasHorizontalScroller_(false) setBackgroundColor_(myApp's class "NSColor"'s clearColor) setAutoresizingMask_(freeSizeWidthHeight) end tell set textView1 to myApp's class "NSTextView"'s alloc()'s initWithFrame:theRect tell textView1 setMinSize_(myApp's NSMakeSize(0, h)) setMaxSize_(myApp's NSMakeSize(10000, 10000)) setVerticallyResizable_(true) setHorizontallyResizable_(false) setAutoresizingMask_(widthSizable) setString_("") setEditable_(false) textContainer()'s setContainerSize:(myApp's NSMakeSize(w, 10000)) textContainer()'s setWidthTracksTextView:true end tell scrollView1's setDocumentView:textView1 view1's addSubview:scrollView1 (* Button 4 *) set theRect to myApp's NSMakeRect(windowWidth - 230, 55, 80, 24) set button4 to myApp's class "NSButton"'s alloc()'s initWithFrame:theRect tell button4 setBezelStyle_(myApp's NSBezelStyleRounded) setTitle_("閉じる") setTarget_(me) setAction_("buttonAction4:") setAutoresizingMask_(fixedButtomAndRight) end tell view1's addSubview:button4 -- set theRect to myApp's NSMakeRect(windowWidth - 320, 55, 80, 24) set button5 to myApp's class "NSButton"'s alloc()'s initWithFrame:theRect tell button5 setBezelStyle_(myApp's NSBezelStyleShadowlessSquare) setTitle_("ゴミ箱へ") setTarget_(me) setAction_("buttonAction5:") setAutoresizingMask_(fixedButtomAndRight) end tell view1's addSubview:button5 ---------------------------------------------------------- on error errText log errText end try
end makeObject on buttonAction1:sender tell application "Mail" to set mailList to messages of inbox my getMailList(mailList) end buttonAction1: on buttonAction2:sender tell application "Mail" to set mailList to selection my getMailList(mailList) end buttonAction2: on buttonAction3:sender button1's setEnabled:false button2's setEnabled:false button3's setEnabled:false textField1's setAlphaValue:0.8 tell progressIndicator1 setAlphaValue_(1.0) setDoubleValue_(0.0) setIndeterminate_(true) startAnimation_(true) end tell tell application "Mail" to set aList to messages of inbox set mailList to {} repeat with aMail in aList (* 中断受付 *) set bitKey to 18 -- {Shift=17,Control=18,Option=19,Command=20} set bitNo to 2 ^ bitKey set ans to myApp's class "NSEvent"'s modifierFlags() if ((ans div bitNo) mod 2) = 1 then (textField1's setAlphaValue:0) (progressIndicator1's setAlphaValue:0.0) (button1's setEnabled:true) (button2's setEnabled:true) (button3's setEnabled:true) return -- "STOP" end if tell application "Mail" set f to read status of aMail if f = false then set ans to aMail set mailList to mailList & {ans} end if end tell end repeat tell progressIndicator1 stopAnimation_(true) setIndeterminate_(false) end tell my getMailList(mailList) end buttonAction3: on buttonAction4:sender tableView1's setEnabled:true view1's setHidden:true end buttonAction4: on buttonAction5:sender set tagetMail to item (mailNo + 1) of mailList delete tagetMail if mailNo = 0 then set sourceList to items 2 thru -1 of sourceList else if mailNo = ((count of sourceList) - 1) then set sourceList to items 1 thru -2 of sourceList else set sourceList to (items 1 thru mailNo of sourceList) & (items (mailNo + 2) thru -1 of sourceList) end if set theDataSource to myApp's class "NSMutableArray"'s alloc()'s init() theDataSource's addObjectsFromArray:sourceList tableView1's reloadData() tableView1's setEnabled:true view1's setHidden:true end buttonAction5: on tableAction:sender textView1's setString:"" textView1's setBackgroundColor:(myApp's class "NSColor"'s colorWithCalibratedRed:0.9 green:0.93 blue:0.97 alpha:1.0) tableView1's setEnabled:false delay 0.3 view1's setHidden:false set mailNo to sender's clickedRow() set tagetMail to item (mailNo + 1) of mailList set {text1, text2} to mailEmergencyCheck(tagetMail) set commentText to return & text2 & return & return & return & text1 textView1's setString:commentText if text2 ≠ "" then tableView1's setBackgroundColor:(myApp's class "NSColor"'s colorWithCalibratedRed:1.0 green:0.95 blue:0.95 alpha:0.8) end if end tableAction: on getMailList(mailList) -- メールリスト取得 button1's setEnabled:false button2's setEnabled:false button3's setEnabled:false tableView1's setAlphaValue:0.5 progressIndicator1's setAlphaValue:1.0 textField1's setAlphaValue:0.8 try set sourceList to {} set countMail to count of mailList set i to 0 repeat with aMail in mailList (* 中断受付 *) set bitKey to 18 -- {Shift=17,Control=18,Option=19,Command=20} set bitNo to 2 ^ bitKey set ans to myApp's class "NSEvent"'s modifierFlags() if ((ans div bitNo) mod 2) = 1 then exit repeat -- "STOP" -- set i to i + 1 set aCheckText to "-" set {text1, text2} to mailEmergencyCheck(aMail) if text2 contains "危険" then set aCheckText to "危険" else if text2 contains "注意" then set aCheckText to "注意" else set aCheckText to "-" end if tell application "Mail" set aAddress to sender of aMail if aAddress contains ">" then set aAddress to rich text ((offset in aAddress of "<") + 1) thru ((offset in aAddress of ">") - 1) ¬ of aAddress set aSubject to subject of aMail set aDate to (date sent of aMail) as rich text end tell set sourceList to sourceList & {{data1:aCheckText, data2:aAddress, data3:aSubject, data4:aDate}} set c to count of sourceList if ((c mod 5) = 0) and (c < 50) then set theDataSource to myApp's class "NSMutableArray"'s alloc()'s init() (theDataSource's addObjectsFromArray:sourceList) tableView1's reloadData() end if set aValue to i / countMail (progressIndicator1's setDoubleValue:aValue) end repeat set theDataSource to myApp's class "NSMutableArray"'s alloc()'s init() theDataSource's addObjectsFromArray:sourceList tableView1's reloadData() on error errText log errText end try tableView1's setAlphaValue:1.0 button1's setEnabled:true button2's setEnabled:true button3's setEnabled:true progressIndicator1's setAlphaValue:0.0 textField1's setAlphaValue:0.0 end getMailList on mailEmergencyCheck(aMail) -- 危険度判定 set CR to ASCII character 13 tell application "Mail" set aSender to sender of aMail set aSubject to subject of aMail set aContent to content of aMail set aHeaders to all headers of aMail end tell set commentText to "● 送信者:" & CR & " " & aSender & CR & "● メールタイトル:" & CR & " " & aSubject & CR & "● 本文:" & CR & ¬ aContent & CR & CR set emergencyText to "" (* 二重アドレス偽装の確認 -『???@???.jp.xxx.xxx』のようなアドレス偽装かどうか? *) if (aSender contains ".co.jp.") or (aSender contains ".or.jp.") or (aSender contains ".ad.jp.") or (aSender contains ".ne.jp.") or ¬ (aSender contains ".com.") then set emergencyText to emergencyText & "危険:送信者のアドレスに異常を発見" & CR if (aSender contains ".net.") or (aSender contains ".org.") then set emergencyText to emergencyText & "危険:送信者のアドレスに異常を発見" & CR (* 末尾が、”.jp”や”.com” などでないものではないのか? *) set add to aSender if (character -1 of add) = ">" then set add to text 1 thru -2 of add if not ((add ends with ".jp") or (add ends with ".com") or (add ends with ".net") or (add ends with ".org")) then set emergencyText to emergencyText & "注意:送信者のドメインが想定外でした。" & CR (* 本文中に二重アドレス偽装がないか? *) if (aContent contains ".co.jp.") or (aContent contains ".or.jp.") or (aContent contains ".ad.jp.") or (aContent contains ".ne.jp.") or ¬ (aContent contains ".com.") then set emergencyText to emergencyText & "危険:本文中のアドレスに異常を発見" & CR if (aSender contains ".net.") or (aSender contains ".org.") then set emergencyText to emergencyText & ¬ "危険:本文中のアドレスに異常を発見" & CR (* ヘッダーに異常 1 *) repeat with oneLine in (paragraphs of aHeaders) if (oneLine contains "Message-ID") then --log oneLine set senderAdd to text ((offset in oneLine of "@") + 1) thru ((offset in oneLine of ">") - 1) of oneLine repeat with oneLine in (paragraphs of aHeaders) if (oneLine contains senderAdd) and (oneLine contains "Received") and (oneLine contains "(unknown [") then set emergencyText to emergencyText & ¬ "注意:ヘッダーの送信者情報にunknownを発見" & CR & " (たまに設定していないだけのサーバーがあるので一概には...)" & CR exit repeat end if end repeat end if end repeat (* ヘッダーに異常 2 *) set objList to {{"Amazon", "amazon"}, {"アマゾン", "amazon"}, {"Rakuten", "rakuten"}, {"楽天", "rakuten"}, ¬ {"イオンカード", "aeon.co.jp"}, {"三井住友カード", "smbc-card.com"}, {"エポスカード", "eposcard.co.jp"}, ¬ {"セゾンカード", "saisoncard.co.jp"}, {"セブンカード", "7card.co.jp"}, {"Suica", "suica"}, {"コスモ", "cosmo"}, ¬ {"三菱UFJカード", "mufg.jp"}, {"ライフカード", "lifecard-promotion-dg.com"}, {"eneos", "eneos.co.jp"}, ¬ {"Americanexpress", "americanexpress.com"}, {"アメリカン・エキスプレス", "americanexpress.com"}, ¬ {"JR西日本", "jr-odekake"}, {"J-WEST", "jr-odekake"}, {"Adobe", "adobe.com"}, {"アドビ", "adobe.com"}, ¬ {"マイクロソフト", "microsoft"}, {"Microsoft", "microsoft"}, {"JR東日本", "jr-odekake"}, {"J-EAST", "jr-odekake"}, ¬ {"郵便局", "japanpost.jp"}} -- , {"", ""} repeat with oneLine in (paragraphs of aHeaders) if (oneLine contains "差出人: ") then set c to offset in oneLine of "<" if c > 1 then set text1 to text 1 thru (c - 1) of oneLine set text2 to text (offset in oneLine of "@") thru -1 of oneLine repeat with obj in objList set {key1, key2} to obj if text1 contains key1 then if not (text2 contains key2) then set emergencyText to emergencyText & "危険:送信者名に偽装の可能性" & CR end if end repeat end if exit repeat end if end repeat -- if emergencyText = "" then set emergencyText to "(異常は発見されませんでした。)" return {commentText, emergencyText} end mailEmergencyCheck (* TableViewの表示行数処理(必須) *) on numberOfRowsInTableView:tableView1 set c to count of (my theDataSource) return c end numberOfRowsInTableView: (* TableViewデータの処理(必須) *) on tableView:tableView1 objectValueForTableColumn:aColumn row:aRow set aRec to (my theDataSource)'s objectAtIndex:(aRow as number) set aIdentifier to (aColumn's identifier()) as string set aRes to (aRec's valueForKey:aIdentifier) return aRes end tableView:objectValueForTableColumn:row: (* ウインドウバーの閉じるボタンアクションを受けた時 *) on windowShouldClose:sender set closeFlg to true end windowShouldClose: (* ウインドウを閉じるための実際のアクション *) on closeWin:aWindow aWindow's orderOut:me if (name of myApp) ≠ "Script Editor" then quit me end closeWin: |