Menu

Skip to content
AppleScriptの穴
  • Home
  • Products
  • Books
  • Docs
  • Events
  • Forum
  • About This Blog
  • License
  • 仕事依頼

AppleScriptの穴

Useful & Practical AppleScript archive. Click '★Click Here to Open This Script' Link to download each AppleScript

タグ: Safari

AppleScriptによるWebブラウザ自動操縦ガイドをmacOS 13対応アップデート

Posted on 2月 22 by Takaaki Naganoya

SafariやGoogle Chromeなど7つのmacOS用Webブラウザを操作するAppleScriptの書き方と有用な実例を紹介する電子書籍「AppleScriptによるWebブラウザ自動操縦ガイド」をアップデートしたv1.3を公開しました。ページ数も317から330ページに増加。macOS 12, Monterey & 13 Ventura, Apple Silicon Mac対応です。

→ 販売ページ

目次

1章 めんどうな操作を自動化しよう!
人間の手で操作して情報を集めたり、データ入力するのは非効率。スクリプトから操作して自動操作

2章 スクリプトエディタの使い方
AppleScript専用のスクリプトエディタの使い方など、基礎的な内容をご紹介。

3章 WebブラウザをAppleScriptから動かそう
macOS用Webブラウザの大半はAppleScriptから操作可能。AppleScript対応は必須の機能!

4章 Webブラウザの情報を取り出そう
Webブラウザ自体が大量の情報を管理しています。まずは、ブラウザの情報を調べてみましょう。

5章 指定URLをオープンしよう
誰にでも確実に行える操作です。かならず経験しておきましょう。URLをオープンした後が大事です。

6章 コンテンツをキャプチャして保存しよう
表示内容を変化しないデータや、再利用可能なデータとして残しておくことは、重要な処理です。

7章 Webコンテンツにアクセスしよう
HTML内の操作対象にアクセスするための、さまざまなアプローチをご紹介

8章 Webコンテンツを画面部品として操作しよう
画面上の部品と同様にWebコンテンツにアクセスして強引に操作する「奥の手」GUI Scripting

9章 ログイン、ログアウトしてみよう
ユーザー登録が必要なWebサイトの処理を行うために必須の作業です。意外とクリアしにくい箇所

10章 データをダウンロードして処理しよう
直接ダウンロードできないファイルのダウンロード完了を検出して、ファイル処理しよう! ダウンロード後にファイル整理したりファイル名を変更したり

11章 仮想ディスプレイでユーザーの誤操作を防ごう
ユーザーからの操作をガードするために、仮想ディスプレイを用意してWebブラウザを表示! 画面上から強引にScriptで動かすと、ユーザーの誤操作が一番の大敵

12章 さまざまな実行環境を知ろう
AppleScriptにはいろいろな実行環境があって、環境ごとにできる/できないことがあります。時間と気持ちに余裕のある時にでも読んでおくとよいでしょう。

13章 実例:Quoraの統計データを取得しよう
実際に、Quoraのアクセス情報ページにアクセスし、自分の投稿情報を取得してみよう!

14章 さまざまな技術資料。興味があったら読んでね
アプリケーション・オブジェクトの指定方法/AppleScript用語辞書の確認方法/Webブラウザの基礎的なScripting/Google Chrome系のWebブラウザのsdef/AppleScriptの歴史/AppleScriptのエラーコード表/各Webブラウザの用語辞書の変更履歴/AppleScript予約語一覧

(Visited 16 times, 1 visits today)
Posted in Books news | Tagged 12.0savvy 13.0savvy Brave Browser Google Chrome Google Chromium Microsoft Edge Opera Safari Vivaldi | Leave a comment

Safari v16がリリースされる

Posted on 9月 14, 2022 by Takaaki Naganoya

Safari v16がリリースされました。AppleScript用語辞書的には、v15.6.1からとくに変更はありません。

ただし、v15.0.4から比べると、AppleScript用語辞書のサイズが半分に。よくよく調べてみると、v15.6.1のあたりでStandard Suitesの定義をxincludeで外部から参照するように変更されていました。

また、v15.6.1からはSafariのAppleScript用語辞書はきちんと清書されて改行が削除されない状態で入れられるようになったようです。

AppleScript用語辞書の改行削除は、Adobe InDesignやIllustratorあたりで見かけた小技で、改行コードを削除した分、当時のCPUで苦労していた辞書のParse処理負荷を軽減することを目的としていたようです。

ただ、その内容をどうやってメンテナンスしているのかは疑問で、もともとの改行が入ったAppleScript用語辞書が維持されていなければ、どう編集してよいかわからないことでしょう。

なので、今後も用語辞書を維持・変更していくうえでは、改行コードつきのAppleScript用語辞書のままであることは重要です。

とはいえ、Safari v16はAppleScript的には何も新しい機能はありません。

(Visited 33 times, 1 visits today)
Posted in sdef | Tagged 12.0savvy Safari | Leave a comment

macOS 12.5RC(21G69)、一定以上の長さのapplescript:// URL Linkを受け付けない???

Posted on 7月 14, 2022 by Takaaki Naganoya

毎回毎回、何かかしら嫌がらせをブチ込んでくるmacOS 12.5シリーズ、RC(21G69)で「applescript://」Custom URL Linkが一定以上の長さになると無視されることが判明しました。

これは、本Blogなどに掲載しているURLリンク入りのプログラムリストの機能を無効にするものです。

この動作は、macOS 10.15のPDFViewで行った嫌がらせと同じ仕様です。さんざん文句を言ったところ、Preview.app上でのURLイベントクリックについてはStop、他のアプリケーションのWindow上のPDFViewについては嫌がらせをしなくなったという経緯があります。

自分も本件はAppleにバグレポートしますが、Appleはレポート件数で重要かそうでないか判断するので、本Blogをご覧の方々にもAppleへのレポートをお願いしたいところです。

–> Demo Movie of macOS 12.5RC applescript:// URL Link ignorance

(Visited 67 times, 1 visits today)
Posted in Bug | Tagged Safari Script Editor | 4 Comments

Safariで表示中のYouTubeムービーのサムネイル画像を取得

Posted on 5月 9, 2022 by Takaaki Naganoya

Safariで表示中のYouTubeムービーのサムネール画像を取得、保存、表示するAppleScriptです。

YouTubeのムービーのサムネール画像の取得方法を確認し、動作確認用にダイアログ表示+画像保存の機能を追加したものです。Script Debugger上で動かしている分には、NSImageの内容を結果表示ビューワで自動表示されますが、ない人向けに付けた機能です。

画像自体は、「ピクチャ」フォルダにUUIDつきでPNG形式で保存します。

–> Download Script bundle with Library

掲載リストには、画像表示ライブラリが含まれていないため、そのままでは実行できません。上記のScript Bundleをダウンロードして実行する必要があります。

AppleScript名:Safariで表示中のYouTubeムービーのサムネイル画像を取得.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2022/05/09
—
–  Copyright © 2022 Piyomaru Software, All Rights Reserved
—

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
use imgLib : script "imageDisplayLib"

property NSUUID : a reference to current application’s NSUUID
property |NSURL| : a reference to current application’s |NSURL|
property NSString : a reference to current application’s NSString
property NSImage : a reference to current application’s NSImage
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSURLComponents : a reference to current application’s NSURLComponents
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSMutableDictionary : a reference to current application’s NSMutableDictionary

tell application "Safari"
  tell front document
    try
      set aURL to URL
    on error
      set aURL to "https://www.youtube.com/watch?v=_fmDtIV9vvI"
    end try
  end tell
end tell

if aURL does not start with "https://www.youtube.com/watch?" then return

set urlDict to parseURLParamsAsDict(aURL) of me
set aParam to urlDict’s valueForKey:"v"
if aParam = missing value then return

set imgURL to "https://i1.ytimg.com/vi/" & (aParam as string) & "/mqdefault.jpg"
set newURL to |NSURL|’s URLWithString:imgURL
set aImg to NSImage’s alloc()’s initWithContentsOfURL:newURL

set imgPath to (POSIX path of (path to pictures folder) & ((aParam as string) & "_") & (NSUUID’s UUID()’s UUIDString()) as string) & ".png"

–Save
saveNSImageAtPathAsPNG(aImg, imgPath) of me

–Display
dispImage(aImg, "YouTube thumbnail") of imgLib

on parseURLParamsAsDict(aURL)
  set components to NSURLComponents’s alloc()’s initWithString:aURL
  
set qList to (components’s query())’s componentsSeparatedByString:"&"
  
  
set paramRec to NSMutableDictionary’s dictionary()
  
  
repeat with i in qList
    set keyAndValues to (i’s componentsSeparatedByString:"=")
    (
paramRec’s setObject:(keyAndValues’s objectAtIndex:1) forKey:(keyAndValues’s objectAtIndex:0))
  end repeat
  
  
return paramRec
end parseURLParamsAsDict

–NSImageを指定パスにPNG形式で保存
on saveNSImageAtPathAsPNG(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 –成功ならtrue、失敗ならfalseが返る
end saveNSImageAtPathAsPNG

★Click Here to Open This Script 

(Visited 98 times, 1 visits today)
Posted in Image Record Text URL | Tagged 10.14savvy 10.15savvy 11.0savvy 12.0savvy Safari | Leave a comment

新刊発売:AppleScriptによるWebブラウザ自動操縦ガイド

Posted on 3月 2, 2022 by Takaaki Naganoya

macOS用の7つのWebブラウザ(Safari、Chrome、Chromium、Microsoft Edge、Vivaldi、Brave Browser、Opera)を操作するAppleScript本です。PDF形式306ページ。

→ 販売ページ

1章 めんどうな操作を自動化しよう!

人間の手で操作して情報を集めたり、データ入力するのは非効率。スクリプトから操作して自動操作

2章 スクリプトエディタの使い方

AppleScript専用のスクリプトエディタの使い方など、基礎的な内容をご紹介。

3章 WebブラウザをAppleScriptから動かそう

macOS用Webブラウザの大半はAppleScriptから操作可能。AppleScript対応は必須の機能!

4章 Webブラウザの情報を取り出そう

Webブラウザ自体が大量の情報を管理しています。まずは、ブラウザの情報を調べてみましょう。

5章 指定URLをオープンしよう

誰にでも確実に行える操作です。かならず経験しておきましょう。URLをオープンした後が大事です。

6章 コンテンツをキャプチャして保存しよう

表示内容を変化しないデータや、再利用可能なデータとして残しておくことは、重要な処理です。

7章 Webコンテンツにアクセスしよう

HTML内の操作対象にアクセスするための、さまざまなアプローチをご紹介

8章 Webコンテンツを画面部品として操作しよう

画面上の部品と同様にWebコンテンツにアクセスして強引に操作する「奥の手」GUI Scripting

9章 ログイン、ログアウトしてみよう

ユーザー登録が必要なWebサイトの処理を行うために必須の作業です。意外とクリアしにくい箇所

10章 データをダウンロードして処理しよう

直接ダウンロードできないファイルのダウンロード完了を検出して、ファイル処理しよう! ダウンロード後にファイル整理したりファイル名を変更したり

11章 仮想ディスプレイでユーザーの誤操作を防ごう

ユーザーからの操作をガードするために、仮想ディスプレイを用意してWebブラウザを表示! 画面上から強引にScriptで動かすと、ユーザーの誤操作が一番の大敵

12章 さまざまな実行環境を知ろう

AppleScriptにはいろいろな実行環境があって、環境ごとにできる/できないことがあります。時間と気持ちに余裕のある時にでも読んでおくとよいでしょう。

13章 実例:Quoraの統計データを取得しよう

実際に、Quoraのアクセス情報ページにアクセスし、自分の投稿情報を取得してみよう!

14章 さまざまな技術資料。興味があったら読んでね

アプリケーション・オブジェクトの指定方法/AppleScript用語辞書の確認方法/Webブラウザの基礎的なScripting/Google Chrome系のWebブラウザのsdef/AppleScriptの歴史/AppleScriptのエラーコード表/AppleScript予約語一覧/各Webブラウザの用語辞書の変更履歴

(Visited 125 times, 1 visits today)
Posted in Books news | Tagged Brave Browser Chromium Google Chrome Opera Safari Vivaldi | Leave a comment

SafariでブックマークされたURL一覧を取得

Posted on 1月 7, 2022 by Takaaki Naganoya

Safariのブックマークに登録されたURL一覧を取得するAppleScriptです。

SafariのAppleScript対応機能に、ブックマーク操作系のものは存在していません。そのため、Bookmarks.plistを直接読み込んでデータの絞り込みを行なっています。

Bookmarks.plistを実際に読んでみると、ツリー(フォルダ)とリーフ(個別のブックマーク)があって、本Scriptではリーフ部分しか読んでいないのですが、とりあえず試作レベルということで。

読むのはいいんですが、追加が大変そうなので(BookmarkのIDとか外部で勝手に追加できるものなんだろうか。UUIDっぽいけど)ブックマーク登録は野蛮にGUI Scripting経由でメニューを操作することになるのでしょう。

AppleScript名:SafariのBookmarks.plistから登録URLを取得
—
–  Created by: Takaaki Naganoya
–  Created on: 2022/01/07
—
–  Copyright © 2022 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.7"
use framework "Foundation"
use scripting additions

script spdB
  property urlList : {}
end script

set (urlList of spdB) to getBookmarkedURLs() of me
set (urlList of spdB) to cleanUp1DList((urlList of spdB), missing value) of me –Sweep missing value items

–Read Bookmarked URLs
on getBookmarkedURLs()
  set newPath to "~/Library/Safari/Bookmarks.plist"
  
set aRec to my readPlistAt:newPath
  
return ((aRec’s Children)’s valueForKey:"URLString") as list
end getBookmarkedURLs

–Read Plist
on readPlistAt:thePath
  set thePath to current application’s NSString’s stringWithString:thePath
  
set thePath to thePath’s stringByExpandingTildeInPath()
  
set theDict to current application’s NSDictionary’s dictionaryWithContentsOfFile:thePath
  
return theDict
end readPlistAt:

–1D Listのスイープ
on cleanUp1DList(aList as list, cleanUpItems as list)
  set bList to {}
  
repeat with i in aList
    set j to contents of i
    
if j is not in cleanUpItems then
      set the end of bList to j
    end if
  end repeat
  
return bList
end cleanUp1DList

★Click Here to Open This Script 

(Visited 85 times, 1 visits today)
Posted in Record | Tagged 10.15savvy 11.0savvy 12.0savvy Safari | Leave a comment

経過時間文字列を秒数に変換する

Posted on 6月 7, 2021 by Takaaki Naganoya

Safariで表示中のダウンロード残り時間の文字列を数値に変換するために作成したAppleScriptです。

Safariで実行中のダウンロード残り時間の一覧情報を表インタフェースで表示する、ごくごく私的な用途のAppleScriptを書いていたときに必要になったものです。

Safariのダウンロード情報表示は、最近のバージョンではポップオーバーで行われるようになっています。そのポップオーバーの上に表インタフェースが配置されていて、その中の各行の中にあるUI Elementを追いかけるとダウンロード残り時間情報などが取得できます。


▲GUI Scriptingに欠かせない。PFiddlesoftのUI Browser。使わないのと使うのとでは、生産性が1億倍ぐらい違うツール

ただし、得られた「残り時間」の文字列をいったんdateオブジェクトに変換して、経過秒数を計算するのに少々骨が折れました。Safariのダウンロード一覧に表示される文字列はあくまでも「結果表示」で、0分とか0時間とか0秒といった桁は表示が省略されます。

当初、「HH時間mm分ss秒」という固定フォーマットだと勝手に思い込んで処理していました。ところが、各桁が省略されるパターンに遭遇。固定文字列でフォーマット指定をして評価しようとしても、いろいろと無理がありました。自然言語っぽい表記で経過時間の情報を与えられたときに、それを評価してdateオブジェクトに変換するのはけっこうな手間がかかります。

「時」「分」「秒」の各情報が存在するかどうかチェックし、各パターンに合わせた日付フォーマッター文字列を場合分けで書いてみたものの、前述のとおり途中の桁が省略されるパターンに遭遇。3つの桁情報の有無に対応するだけでも、3x2x1=6パターンに対応するif文が必要。さらに、「年」「月」「日」まで表示される可能性を考慮すると、桁要素が6個になるため、6x5x4x3x2x1=720パターンに対処するif文が必要に。この、if文で固定フォーマット文字列を場合分けで選択するというアプローチでは処理が破綻することがあきらかです(if文をそんなに書いたら見通しが悪くなって、矛盾した表記が混入してもわからないでしょう。そこはかとなく、頭が悪い感じもします)。

そこで、与えられた経過時間文字列を直接チェックして、「経過時間文字列に合うように」NSDateFormatter用の文字列を「動的に」組み立てるように方針を転換。これで、大幅に簡潔な記述で済むようになりました。

一番の誤算は、AppleScript上でNSDateのtimeIntervalSinceReferenceDate()の結果が正しく取得できなかったこと。このメソッドで2000年1月1日からの相対秒を取得するつもりだったのですが、期待したような結果は得られませんでした。そのため、2000年1月1日のdateオブジェクトを用意して引き算で経過秒数を求めました。このあたり、AFS(Apple File System)導入時にタイムスタンプの分解能が増やされたことに影響を受けているのだろうかと疑っています。

用途が果てしなく下らない割にてこずらされました。

AppleScript名:時分秒の文字列を数値に変換 v5
— Created 2021-06-06 by Takaaki Naganoya
— 2021 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set relSecNum to relativeSecondsFromStringWithDateFormat("22時間 21分 15秒") of me
–> 80475

set relSecNum to relativeSecondsFromStringWithDateFormat("22時間 15秒") of me
–> 79215

set relSecNum to relativeSecondsFromStringWithDateFormat("22時間") of me
–> 79200

on relativeSecondsFromStringWithDateFormat(postDateStr as string)
  set sepList to {"年", "月", "日", "時間", "分", "秒"}
  
set formList to {"yyyy", "MM", "dd", "HH", "mm", "ss"}
  
  
set formatStr to ""
  
repeat with i from 1 to 6
    set aSepStr to contents of item i of sepList
    
set aFormStr to contents of item i of formList
    
if postDateStr contains aSepStr then
      set formatStr to formatStr & (aFormStr) & (aSepStr)
    end if
  end repeat
  
  
set postDate to dateFromStringWithDateFormat(postDateStr, formatStr) of me
  
  
set origDate to dateFromStringWithDateFormat("0時間 0分 0秒", "HH時間mm分ss秒") of me
  
set aSeconds to postDate – origDate
  
return aSeconds
end relativeSecondsFromStringWithDateFormat

on dateFromStringWithDateFormat(dateString as string, dateFormat as string)
  set dStr to current application’s NSString’s stringWithString:dateString
  
set dateFormatStr to current application’s NSString’s stringWithString:dateFormat
  
set aDateFormatter to current application’s NSDateFormatter’s alloc()’s init()
  
aDateFormatter’s setDateFormat:dateFormatStr
  
aDateFormatter’s setLocale:(current application’s NSLocale’s alloc()’s initWithLocaleIdentifier:"en_US_POSIX")
  
set aDestDate to (aDateFormatter’s dateFromString:dStr)
  
return aDestDate as date
end dateFromStringWithDateFormat

★Click Here to Open This Script 

(Visited 166 times, 1 visits today)
Posted in Calendar Text | Tagged 11.0savvy Safari | Leave a comment

Safariで表示中のタブを順次切り替え

Posted on 12月 26, 2020 by Takaaki Naganoya

StreamDeckから呼び出すことを前提に作ってみました。StreamDeckの実機は友人に返却したものの、ソフトウェア版のStreamDeck(iOSデバイスで動く)が稼働するので、iPhone上のStreamDeckアプリでMacをコントロール可能です。

AppleScript名:rotate tabs.scpt
tell application "Safari"
  tell front window
    set tCount to count every tab
    
set curTab to index of current tab
    
    
if curTab = tCount then
      set targTab to 1
    else
      set targTab to curTab + 1
    end if
    
    
set current tab to tab targTab
  end tell
end tell

★Click Here to Open This Script 

こんなAppleScriptを書いて、StreamDeckの設定ソフトウェアで「開く」アクションにAppleScriptを登録。

実行時、初回だけ「Safariのコントロールを許可するか?」というセキュリティダイアログが出てきますが、許可すれば2回目以降で聞かれることはありません。

(Visited 420 times, 4 visits today)
Posted in Peripheral | Tagged 10.14savvy 10.15savvy 11.0savvy Safari Streamdeck | Leave a comment

LAN上の別のMacでYouTubeムービー再生をハンズオーバー v2

Posted on 10月 8, 2020 by Takaaki Naganoya

Safariの最前面のウィンドウで再生中のYouTubeムービーの情報を取得し、LAN上の別のMacで再生を引き継ぐ(ハンズオーバーする)AppleScriptです。

macOS 10.13以降、リモートAppleEvents経由でGUIアプリケーションを直接操作する機能が復活しました(Mac OS X 10.7〜10.12ぐらいまでAppleScriptアプレット間のみリモート通信が許可されていた状態)。

メインマシンで再生中のYouTubeムービーを、LAN上の他のマシンに引き継がせてみました。再生を引き継がれる方のマシンでは、システム環境設定の「共有」で「リモートApple Events」の項目をオンにしています(自分のマシンではすべてこの項目をオンにしています)。

(1)リモートマシン上のユーザーのパスワード

AppleScript書類のコメント(Finderコメント)にパスワードを書いておくと、それを読み取って使用するようにしてみました。

(2)リモートマシン上のSafariの起動

リモートマシン上のアプリケーションの操作は、ただリモートマシン上のアプリケーションを指定すればOKなのですが、操作対象のアプリケーションが起動していない場合にはエラーになります。これは、とても困る仕様です。

そこで、リモートマシンのFinder経由でアプリケーションファイルをオープンすることで、リモートマシン上でSafariを起動します。オープン対象をapplication file “Safari”と指定するとエラーになりますが、application file id “com.apple.Safari”と指定するとエラーになりません。

(3)YouTubeで再生中の情報取得

以前調査しておいた内容をそのまま使っています。再生中ならPauseし、再生中の位置(時間)情報を取得し、文字列で指定するために加工してYouTubeのURLに追加しています。URLの加工部分は少々手抜きをしています。

とくに問題なく、メインマシンから他のマシン(macOS 10.15.7/macOS 11.0beta9)にLAN経由で再生をハンズオーバーできました。

実際に、コントロール先のマシン名(Bonjour名)をremoteMachineNameに、ユーザー名をremoteUserNameに、パスワードを実行するAppleScript書類のFinderコメントに書き込んで実行してください。スクリプトエディタ上でもスクリプトメニューからでも問題なく実行できています。

あとは、Safari上のYouTube再生をフルスクリーンで行えるとよいのですが、少し試した範囲ではできなかったので、また地道に調べておく感じでしょうか。

AppleScript名:LAN上の別のMacでYouTubeムービー再生をハンズオーバー v2.scpt
—
–  Created by: Takaaki Naganoya
–  Created on: 2020/10/08
—
–  Copyright © 2020 Piyomaru Software, All Rights Reserved
—

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

property |NSURL| : a reference to current application’s |NSURL|
property NSMetadataItem : a reference to current application’s NSMetadataItem

set remoteMachineName to "MacMini2014.local"
set remoteUserName to "maro"

–ScriptのCommentに書いておいたパスワードを拾って使う
set mePath to (path to me)

–FinderからCommentは拾えるが、メタデータ経由で取得する処理も試してみた
set remoteUserPass to getFinderComment(POSIX path of mePath) of me

tell application "Safari"
  if running then
    tell front document
      set aURL to URL
      
–最前面のウィンドウがYouTubeの場合のみ処理
      
if aURL does not start with "https://www.youtube.com/" then
        display notification "エラー: YouTubeを再生していないため、ハンズオーバーしませんでした"
        
return
      end if
      
      
–再生中のポジションを取得
      
set tRes to (do JavaScript "document.querySelector(’#movie_player video’).currentTime;")
      
      
–再生状況を取得
      
set pRes to (do JavaScript "document.querySelector(’#movie_player video’).paused;")
      
      
if pRes = false then
        –再生中であればPauseする
        
set aRes to (do JavaScript "document.querySelector(’#movie_player .ytp-play-button’).click();")
      end if
      
      
openYouTubeOnRemoteMachine(remoteUserName, remoteUserPass, remoteMachineName, tRes, aURL) of me
    end tell
  end if
end tell

–指定のリモートマシン上のSafariでYouTubeの指定ムービーの指定箇所からの再生を行う
on openYouTubeOnRemoteMachine(remoteUser, remotePass, remoteMachineLocal, newDuration, newURL)
  set remoteMachineName to "eppc://" & remoteUser & ":" & remotePass & "@" & remoteMachineLocal
  
  
–URLの加工。ちょっと手抜きをした
  
if newDuration is not 0 then
    set tText to retTimeText(newDuration) of me
    
if newURL contains "&" then
      set sepChar to "?"
    else
      set sepChar to "&"
    end if
    
    
set newURL to newURL & sepChar & "t=" & tText
  end if
  
  
using terms from application "Safari"
    tell application "Safari" of machine remoteMachineName
      if not running then
        –起動していなかったらあらためてSafariを起動
        
launchRemoteSafari(remoteMachineName) of me
      end if
      
      
try
        close every document
      end try
      
      
set aWin to make new document
      
      
tell aWin
        set URL to newURL
        
–フルスクリーン再生をためしてみたが、こういう書き方ではなかった模様(URLオープンを待つ必要もある)
        
–set aRes to (do JavaScript "document.querySelector(’#movie_player playFullscreen’).click();")
      end tell
      
    end tell
  end using terms from
end openYouTubeOnRemoteMachine

–リモートマシン上でSafariを起動する
on launchRemoteSafari(aMachine)
  using terms from application "Finder"
    tell application "Finder" of machine aMachine
      open application file id "com.apple.Safari"
    end tell
  end using terms from
end launchRemoteSafari

–数値を「h」「m」「s」でフォーマットして返す
on retTimeText(aTime)
  set aHour to aTime div 3600
  
set aMinute to (aTime – (aHour * 3600)) div 60
  
set aSeconds to (aTime mod 60)
  
  
set aString to ""
  
  
if aHour > 0 then
    set aString to aHour & "h"
  end if
  
  
if aMinute > 0 then
    set aString to aString & (aMinute as integer) & "m"
  end if
  
  
if aSeconds > 0 then
    set aString to aString & (aSeconds as integer as string) & "s"
  end if
  
  
return (aString as string)
end retTimeText

–Finderコメントをメタデータ経由で取得
on getFinderComment(aPOSIX)
  set aURL to |NSURL|’s fileURLWithPath:aPOSIX
  
set aMetaInfo to NSMetadataItem’s alloc()’s initWithURL:aURL
  
set metaDict to (aMetaInfo’s valuesForAttributes:{"kMDItemFinderComment"}) as record
  
if metaDict = {} then return ""
  
set aComment to kMDItemFinderComment of (metaDict)
  
return aComment
end getFinderComment

★Click Here to Open This Script 

(Visited 103 times, 1 visits today)
Posted in Internet JavaScript Remote Control | Tagged 10.13savvy 10.14savvy 10.15savvy 11.0savvy Finder NSMetadataItem NSURL Safari | 14 Comments

Mac App StoreでTable Dripper v1.1が公開に

Posted on 10月 7, 2020 by Takaaki Naganoya

Webブラウザで表示中の範囲にあるTableデータを書き出すアプリケーション「Table Dripper」の新バージョンv1.1の審査が終わって公開になりました。macOS 10.14以降に対応しています。

新バージョンでは、かねてよりお知らせしていたとおりWebブラウザ「Brave Browser」と「Vivaldi」のサポートを追加。さらに、表データ抽出時に行ヘッダーを削除して出力する機能と、表データのHTML書き出しをサポートしています。

Table Dripper v1.1サポートWebブラウザ:Safari、Google Chrome、Microsoft Edge、Brave Browser、Vivaldi

今回のMac App Storeの審査は5日ほど。新作を出したときと同じぐらいの期間がかかっています。普通、アップデート版の審査はあまり時間がかからないのですが、本アプリケーションはv1.0のリリース後にApple側から「Mac App Storeの基準に違反しているのでアップデートで修正しろ」と言われた経緯があります。

当然、Apple社内的には審査をゼロからやり直し、初版リリース時と同じぐらいの時間がかかる可能性も覚悟していました(10日はかかりすぎですが)。5日はだいたい標準的な審査時間ではないでしょうか(新作アプリケーションの審査期間としては)。

今回のv1.1で一番大変だったのは、表ヘッダーの削除機能です。

表の全セルの内容を取ってくるのは、HTMLReader.frameworkの機能に依存してそれほど手間をかけずにできるのですが、Tableタグで作られた表は人によって書き方がまちまちで、ヘッダー宣言して行ヘッダーを書いてあるパターンはむしろ少なく、ヘッダーセルの宣言だけで行ヘッダーが作られているケースの方が多いようでした。

# HTMLReader.framework作者のNolan Waiteには毎回、「こんなScriptを作ったよ!」「こんなアプリを公開したよ!」と報告しています。大変感謝しています。当然、Table Dripperの無料コードを送りつけて「よかったら使ってみて!」と押し付けています

日本語のWikipediaに掲載されている表は、構造が単純なものが多く、本アプリケーションによるデータの再利用や流用がやりやすい傾向にあります。

一方で、英語版Wikipediaに掲載されている表は、なぜかメチャクチャ凝ったものが多く、ヘッダー部分のみ抽出するのはけっこう骨が折れます。

tableタグによる表組をいくつかのパターンに分類し、簡単なものから難しいものまでレベル設定を行い、簡単な方から徐々にヘッダー除去できることを確認していきました。v1.1は複数行の行ヘッダーに対応しているものの、ヘッダー列やヘッダー行にcolspanやrowspanなどの宣言を行なっている箇所があると評価エラーになってしまうため、ヘッダーを除去しないでそのまま出力するようにしています。

目下、Xcode上で作成するAppleScript Cocoaアプリケーションは、自分の検証環境がmacOS 10.13以降なのでmacOS 10.13以降対応とするのが自分的な基準なのですが、本アプリケーションについては外部アプリケーション操作やその対応状況の検出などの処理が必要であり、macOS 10.14以降の対応となっています。

macOS 11.0のリリースが目前に控えており、10月なかばにはGM版のリリースが行われることが予想されています(最近GMリリースしないんですけど)。11.0が出た時点でXcode 12.2の正式版も出るはずなので(すごく不透明)、それでARM/Intel 64のUniversal Binary対応とmacOS 11.0上での動作確認したバージョンをひととおり出すことになりそうです。

全部AppleScriptで記述したアプリケーションなので、Universal Binary化も容易(CPU依存部分がゼロ)ですが、macOS 11.0のGUI仕様がBetaのたびにコロコロ変わる(バグなのかなー)ので、そのバグをどう回避するかというあたりで労力が変わってくると思われます。

(Visited 35 times, 1 visits today)
Posted in PRODUCTS | Tagged Brave Browser Google Chrome Microsoft Edge Safari Vivaldi | Leave a comment

Safari 14が配布開始に

Posted on 9月 17, 2020 by Takaaki Naganoya

WebP、WebM、VP9などのデータフォーマットに対応したSafari v14がmacOS 10.14.x、10.15,x向けに配信されました。macOS標準のソフトウェアアップデート経由でアップデートできます。

AppleScript用語辞書(sdef)を前バージョンの13.1.3と比較してみたところ、とくに差異はみあたりません。

(Visited 44 times, 1 visits today)
Posted in Release | Tagged 10.14savvy 10.15savvy 11.0savvy Safari | Leave a comment

Mac App Storeで「Table Dripper」を販売開始

Posted on 9月 11, 2020 by Takaaki Naganoya

例によってAppleScriptで開発したアプリケーション「Table Dripper」のMac App Storeでの販売を開始しました。macOS 10.14以降に対応しています(動作原理的にmacOS 10.13対応は無理だった、、、)。

Safari/Google Chrome/Microsoft Edgeで表示中のWebサイトのTableデータをCSVに変換してダウンロードしてNumbers.appでオープンするアプリケーションです。

Mac App Storeの審査に10日かかって焦りました(機能のコンパクトさに反比例して審査期間が長い。たぶん、開発期間よりも審査期間の方が長い)。

すでに、他のChromiumベースのWebブラウザ「Brave」と「Vivaldi」への対応が済んでおり、次のアップデートでこれらにも対応します。AppleScript用語辞書(sdef)を持っているWebブラウザであっても、do javascript的なコマンドが実装されていないと、本アプリケーションでは対応できません(FireFox、Opera)。まして、AppleScriptにまったく対応していないWebブラウザでは、対応のしようがありません(Sleipnirなど)。

4つのアプリケーションをコントロールするアプリケーションをMac App Storeに申請したのははじめてです。

そろそろ、Script EditorとScript DebuggerをコントロールするアプリケーションをMac App Storeに申請することに。これも、審査が荒れる(期間が長くなる)ことが見込まれます。

(Visited 47 times, 1 visits today)
Posted in PRODUCTS | Tagged 10.14savvy 10.15savvy 11.0savvy Google Chrome Microsoft Edge Safari | Leave a comment

SafariでURLローディング完了検出

Posted on 8月 10, 2020 by Takaaki Naganoya

Safariで指定URLのページローディングを検出するAppleScriptです。

Safari 13で従来どおり、do javascriptコマンド経由で、

if (do JavaScript "document.readyState" in document 1) is "complete" then

などと処理させてみたら、URLが変更される前に”complete”が返ってきました。どうも、この処理が非同期実行されるのか、表示が反映される前に内部的にはページ遷移が完了しているようです。

現行のSafariにおいては、上記の処理ではページローディング完了検出が行えないことが判明。割と利用する機会が多く、重要な処理であるため、書き換えてみました。

いろいろ実験してみると、documentに新規URLを設定しても、すぐにはURLが変更されないようです。その割にAppleScriptの実行は完了したものとして次の行へと進んでしまいます。普通、こんな挙動を行うアプリケーションはないのですが(OCRで見たことがあったかも?)、ただ、逆にここで処理待ちすると問題が発生するケースもありそうです。

非同期モードと同期モードの両方が存在していて、明示的に選択できるとよいのですが、現状ではそうなっていません。

仕方がないので、旧URLから新URLへの切り替えをループで見張るという処理を書いてみたら、いい感じにローディング検出できました。

AppleScript名:SafariでURLローディング検出.scpt
—
–  Created by: Takaaki Naganoya
–  Created on: 2020/08/09
—
–  Copyright © 2020 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set aURL to "http://piyocast.com/as/"
openURLWithSafari(aURL)
set aText to getBodyTextFromSafariFrontWindow() of me

on openURLWithSafari(aURL as string)
  if aURL = "" then error "URL is blank"
  
try
    tell application "Safari"
      set d to count every window
      
if d = 0 then
        make new window
        
tell front document
          set URL to aURL
        end tell
      else
        tell front document
          set oldURL to URL
          
set URL to aURL
        end tell
      end if
      
      
detectPageLoaded(10, oldURL, aURL) of me
      
    end tell
  on error
    return false
  end try
end openURLWithSafari

on detectPageLoaded(timeout_value, oldURL, newURL)
  try
    repeat with i from 1 to (timeout_value * 5)
      tell application "Safari"
        tell front document
          set curURL to URL
        end tell
      end tell
      
      
if curURL = newURL then return
      
delay 0.2
    end repeat
  on error
    return false
  end try
  
  
return false
end detectPageLoaded

on getBodyTextFromSafariFrontWindow()
  –フレームを使っていないことが前提条件
  
tell application "Safari"
    return text of front document
  end tell
end getBodyTextFromSafariFrontWindow

★Click Here to Open This Script 

(Visited 215 times, 1 visits today)
Posted in Internet URL | Tagged 10.13savvy 10.14savvy 10.15savvy 11.0savvy Safari | 1 Comment

アラートダイアログ上にWebViewでGoogle Chartsを表示(Calendar Chart)

Posted on 5月 7, 2020 by Takaaki Naganoya

アラートダイアログ上にWkWebViewを配置し、Google Chartsを用いてCalendar Chartを表示するAppleScriptです。

自分の開発環境(MacBook Pro Retina 2012, Core i7 2.6GHz)で100日間のアクセス履歴を処理して7秒強ぐらいで描画が終了します。

調子に乗って300日分のアクセス履歴を処理したところ、表示まで1分ほどかかりました。あまり長い期間の描画を行わせるのは(このプログラムの書き方だと)向いていないと感じます。いまのところテストしただけで実用性は考えていませんが、この程度のグラフなら自前でNSImage上にボックスを描画して表示しても大した手間にはならないでしょう。


▲スクリプトエディタ上で実行したところ


▲Script Debugger上で実行したところ


▲スクリプトメニュー上で実行したところ

Safariのアクセス履歴は例によってsqliteのDatabaseにアクセスして取得していますが、AppleScriptのランタイム環境によっては、アクセス権限がないというメッセージが出てアクセスできないことがあります。ASObjC Explorer 4上では実行できませんでしたし、Switch Control上でも実行できません。

# 管理者権限つきで実行しても(with administrator privileges)実行できません → Switch Controlでも実行できるようになりました

最初に掲載したバージョンでは、グラフ化したときに表示月が1か月ズレるという問題がありました。

Google Chartsのドキュメントを確認したところ、

Note: JavaScript counts months starting at zero: January is 0, February is 1, and December is 11. If your calendar chart seems off by a month, this is why.

JavaScriptでMonthはJanuaryが0らしく、monthを-1する必要があるとdocumentに書かれていました。うわ、なにその仕様?(ーー;;;

AppleScript名:アラートダイアログ上にWebViewでGoogle Chartを表示(Calendar Charts)v1a.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2020/05/07
—
–  Copyright © 2020 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use framework "WebKit"
use scripting additions

property |NSURL| : a reference to current application’s |NSURL|
property NSAlert : a reference to current application’s NSAlert
property NSString : a reference to current application’s NSString
property NSButton : a reference to current application’s NSButton
property WKWebView : a reference to current application’s WKWebView
property WKUserScript : a reference to current application’s WKUserScript
property NSURLRequest : a reference to current application’s NSURLRequest
property NSRunningApplication : a reference to current application’s NSRunningApplication
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property WKUserContentController : a reference to current application’s WKUserContentController
property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration
property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd

property returnCode : 0

script spd
  property aRes : {}
  
property bRes : {}
end script

–Calculate Safari access frequency for (parameter days)
set (aRes of spd) to calcMain(100) of safariHistLib

set (bRes of spd) to ""
repeat with i in (aRes of spd)
  set {item1, item2, item3} to parseByDelim(theName of (contents of i), "-") of me
  
set newLine to " [ new Date(" & (item1 as string) & ", " & ((item2 – 1) as string) & ", " & (item3 as string) & "), " & (numberOfTimes of i) & "]," & (string id 10)
  
set (bRes of spd) to (bRes of spd) & newLine
end repeat

set (bRes of spd) to text 1 thru -3 of (bRes of spd)

–Pie Chart Template HTML
set myStr to "<!DOCTYPE html>
<html lang=\"UTF-8\">
<head>
<div id=\"calendarchart\"></div>

<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>

<script type=\"text/javascript\">
// Load google charts
google.charts.load(’current’, {’packages’:[’calendar’]});
google.charts.setOnLoadCallback(drawChart);

// Draw the chart and set the chart values
function drawChart() {
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: ’date’, id: ’Date’ });
dataTable.addColumn({ type: ’number’, id: ’Web Access’ });
dataTable.addRows([
  %@
]);

var chart = new google.visualization.Calendar(document.getElementById(’calendar_basic’));

var options = {
title: \"Web Activity\",
height: 350,
};

chart.draw(dataTable, options);
}
</script>
</head>
<body>
  <div id=\"calendar_basic\" style=\"width: 1000px; height: 350px;\"></div>
</body>
</html>"

set aString to current application’s NSString’s stringWithFormat_(myStr, (bRes of spd)) as string

set paramObj to {myMessage:"Calendar Chart Test", mySubMessage:"This is a simple calendar chart using google charts", htmlStr:aString}
–my browseStrWebContents:paramObj–for debug
my performSelectorOnMainThread:"browseStrWebContents:" withObject:(paramObj) waitUntilDone:true

on browseStrWebContents:paramObj
  set aMainMes to myMessage of paramObj
  
set aSubMes to mySubMessage of paramObj
  
set htmlString to (htmlStr of paramObj)
  
  
set aWidth to 1000
  
set aHeight to 300
  
  
–WebViewをつくる
  
set aConf to WKWebViewConfiguration’s alloc()’s init()
  
  
–指定HTML内のJavaScriptをFetch
  
set jsSource to pickUpFromToStr(htmlString, "<script type=\"text/javascript\">", "</script>") of me
  
  
set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true
  
set userContentController to WKUserContentController’s alloc()’s init()
  
userContentController’s addUserScript:(userScript)
  
aConf’s setUserContentController:userContentController
  
  
set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight – 100)) configuration:aConf
  
aWebView’s setNavigationDelegate:me
  
aWebView’s setUIDelegate:me
  
aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true
  
  
set bURL to |NSURL|’s fileURLWithPath:(POSIX path of (path to me))
  
aWebView’s loadHTMLString:htmlString baseURL:(bURL)
  
  
— set up alert  
  
set theAlert to NSAlert’s alloc()’s init()
  
tell theAlert
    its setMessageText:aMainMes
    
its setInformativeText:aSubMes
    
its addButtonWithTitle:"OK"
    
–its addButtonWithTitle:"Cancel"
    
its setAccessoryView:aWebView
    
    
set myWindow to its |window|
  end tell
  
  
— show alert in modal loop
  
NSRunningApplication’s currentApplication()’s activateWithOptions:0
  
my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true
  
  
–Stop Web View Action
  
set bURL to |NSURL|’s URLWithString:"about:blank"
  
set bReq to NSURLRequest’s requestWithURL:bURL
  
aWebView’s loadRequest:bReq
  
  
if (my returnCode as number) = 1001 then error number -128
end browseStrWebContents:

on doModal:aParam
  set (my returnCode) to (aParam’s runModal()) as number
end doModal:

on viewDidLoad:aNotification
  return true
end viewDidLoad:

on fetchJSSourceString(aURL)
  set jsURL to |NSURL|’s URLWithString:aURL
  
set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value)
  
return jsSourceString
end fetchJSSourceString

on pickUpFromToStr(aStr as string, s1Str as string, s2Str as string)
  set a1Offset to offset of s1Str in aStr
  
if a1Offset = 0 then return false
  
set bStr to text (a1Offset + (length of s1Str)) thru -1 of aStr
  
set a2Offset to offset of s2Str in bStr
  
if a2Offset = 0 then return false
  
set cStr to text 1 thru (a2Offset – (length of s2Str)) of bStr
  
return cStr as string
end pickUpFromToStr

–リストを任意のデリミタ付きでテキストに
on retArrowText(aList, aDelim)
  set aText to ""
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retArrowText

on array2DToJSONArray(aList)
  set anArray to current application’s NSMutableArray’s arrayWithArray:aList
  
set jsonData to current application’s NSJSONSerialization’s dataWithJSONObject:anArray options:(0 as integer) |error|:(missing value) –0 is
  
set resString to current application’s NSString’s alloc()’s initWithData:jsonData encoding:(current application’s NSUTF8StringEncoding)
  
return resString
end array2DToJSONArray

on parseByDelim(aData, aDelim)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set dList to text items of aData
  
set AppleScript’s text item delimiters to curDelim
  
return dList
end parseByDelim

script safariHistLib
  property parent : AppleScript
  
use scripting additions
  
use framework "Foundation"
  
  
property |NSURL| : a reference to current application’s |NSURL|
  
  
script spd
    property sList : {}
    
property nList : {}
    
property sRes : {}
    
property dRes1 : {}
    
property dRes2 : {}
  end script
  
  
  
on calcMain(daysNum)
    set (dRes1 of spd) to dumpSafariHistoryFromDaysBefore(daysNum) of me
    
    
set (dRes2 of spd) to {}
    
repeat with i in (dRes1 of spd)
      copy (first item of i) as string to dStr
      
set convDstr to first item of (parseByDelim(dStr, {" "}) of me)
      
set the end of (dRes2 of spd) to convDstr
    end repeat
    
    
–日付ごとに登場頻度集計
    
set cRes to countItemsByItsAppearance2((dRes2 of spd)) of me
    
return cRes as list
  end calcMain
  
  
  
–NSArrayに入れたレコードを、指定の属性ラベルの値でソート
  
on sortRecListByLabel(aArray, aLabelStr as string, ascendF as boolean)
    –ソート
    
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
    
    
–NSArrayからListに型変換して返す
    
set bList to (sortedArray) as list
    
return bList
  end sortRecListByLabel
  
  
  
on dumpSafariHistoryFromDaysBefore(daysBefore)
    –現在日時のn日前を求める
    
using terms from scripting additions
      set origDate to (current date) – (daysBefore * days)
    end using terms from
    
    
set dStr to convDateObjToStrWithFormat(origDate, "yyyy-MM-dd hh:mm:ss") of me
    
    
set aDBpath to "~/Library/Safari/History.db"
    
set pathString to current application’s NSString’s stringWithString:aDBpath
    
set newPath to pathString’s stringByExpandingTildeInPath()
    
    
set aText to "/usr/bin/sqlite3 " & newPath & " ’SELECT datetime(history_visits.visit_time+978307200, \"unixepoch\", \"localtime\"), history_visits.title || \" @ \" || substr(history_items.URL,1,max(length(history_items.URL)*(instr(history_items.URL,\" & \")=0),instr(history_items.URL,\" & \"))) as Info FROM history_visits INNER JOIN history_items ON history_items.id = history_visits.history_item where history_visits.visit_time>(julianday(\"" & dStr & "\")*86400-211845068000) ORDER BY visit_time ASC LIMIT 999999;’"
    
    
using terms from scripting additions
      set (sRes of spd) to do shell script aText
    end using terms from
    
    
set (sList of spd) to (paragraphs of (sRes of spd))
    
    
repeat with i in (sList of spd)
      set j to contents of i
      
      
–Parse each field
      
set j2 to parseByDelim(j, {"|", "@ "}) of me
      
      
set the end of (nList of spd) to j2
    end repeat
    
    
return (nList of spd)
  end dumpSafariHistoryFromDaysBefore
  
  
  
  
on parseByDelim(aData, aDelim)
    set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to aDelim
    
set dList to text items of aData
    
set AppleScript’s text item delimiters to curDelim
    
return dList
  end parseByDelim
  
  
  
–出現回数で集計
  
on countItemsByItsAppearance2(aList)
    set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
    
set bArray to current application’s NSMutableArray’s array()
    
set theEnumerator to aSet’s objectEnumerator()
    
    
repeat
      set aValue to theEnumerator’s nextObject()
      
if aValue is missing value then exit repeat
      
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{"theName", "numberOfTimes"})
    end repeat
    
    
–出現回数(numberOfTimes)で降順ソート
    
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:"theName" ascending:true
    
bArray’s sortUsingDescriptors:{theDesc}
    
    
return bArray
  end countItemsByItsAppearance2
  
  
  
on convDateObjToStrWithFormat(aDateO as date, aFormatStr as string)
    set aDF to current application’s NSDateFormatter’s alloc()’s init()
    
    
set aLoc to current application’s NSLocale’s currentLocale()
    
set aLocStr to (aLoc’s localeIdentifier()) as string
    
    
aDF’s setLocale:(current application’s NSLocale’s alloc()’s initWithLocaleIdentifier:aLocStr)
    
aDF’s setDateFormat:aFormatStr
    
set dRes to (aDF’s stringFromDate:aDateO) as string
    
return dRes
  end convDateObjToStrWithFormat
  
end script

★Click Here to Open This Script 

(Visited 76 times, 1 visits today)
Posted in JavaScript shell script Sort | Tagged 10.13savvy 10.14savvy 10.15savvy NSAlert NSButton NSRunningApplication NSString NSURL NSURLRequest NSUTF8StringEncoding Safari WKUserContentController WKUserScript WKUserScriptInjectionTimeAtDocumentEnd WKWebView WKWebViewConfiguration | Leave a comment

アラートダイアログ上にWkWebViewでGoogle Chartsを表示 v2

Posted on 4月 29, 2020 by Takaaki Naganoya

アラートダイアログ上にWkWebViewを配置し、Google Chartsを用いてPie Chartを表示するAppleScriptです。

自分の開発環境(MacBook Pro Retina 2012, Core i7 2.6GHz)で10日間のアクセス履歴を処理して1.38秒程度でダイアログ表示してグラフ描画に入ります。3秒強ぐらいで描画が終了します。

WkWebViewに対して、文字列で組み立てたHTMLを読み込んでグラフ表示しています。Pie Chart上にマウスカーソルを乗せると反応します。一応表示できているぐらいで、デザイン的には余計な余白ができたままで、いまひとつな感じです。せめて、WkWebViewの背景を透明化できると大味な表示をいくらか緩和できるかと思っているのですが、WkWebViewの透明背景にはNSColorではなく(AppleScriptから作成できない)CGColorでclearColor(透明色)を指定する必要があるようで、、、、

サンプル表示データは、Safariの10日間のアクセス履歴からドメイン単位で計算したヒストグラムを、その場で計算したものを使っています。

本Scriptは、①NSAlertのダイアログ表示の経験 ②WkWebView上へのコンテンツ表示の経験 ③グラフ表示用FrameworkやSVGベースのグラフ作成の経験 を経て、2・3年越しで完成したものです。とくに、WkWebViewについてはサンプル数がそれほど多くなく、用途の方向性がObjective-CやSwiftと若干異なるために、参考にできるサンプルの数が多くない状況。必然的に、いろいろ試しては失敗を重ねてきました。急に出来上がってきたものではありません。

Safariの履歴集計ScriptをScriptオブジェクトの中に放り込んだら、scripting additionsの用語をusing terms from句で囲っておく必要がありました。何回か経験していますが、知らないと対処に困る現象です。

現状(macOS 10.14以降)では、Scripting AdditionはmacOS標準搭載のStandard Additionsしか存在していないし認識も行われないため、エラー内容がいまひとつわかりにくく、不正確であるように思えます。Apple純正のStandard Additionsしか存在していないんだから自分で判断しろやコラ、と言いたくなります。

AppleScript名:アラートダイアログ上にWebViewでGoogle Chartを表示 v2.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2019/03/02
—
–  Copyright © 2019 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use framework "WebKit"
use scripting additions

property |NSURL| : a reference to current application’s |NSURL|
property NSAlert : a reference to current application’s NSAlert
property NSString : a reference to current application’s NSString
property NSButton : a reference to current application’s NSButton
property WKWebView : a reference to current application’s WKWebView
property WKUserScript : a reference to current application’s WKUserScript
property NSURLRequest : a reference to current application’s NSURLRequest
property NSRunningApplication : a reference to current application’s NSRunningApplication
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property WKUserContentController : a reference to current application’s WKUserContentController
property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration
property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd

property returnCode : 0

–Calc Safari access domain from history
set aRes to calcSafariHistoryBest10Domain(10) of safariHistoryLib

set aList to {{"Domain", "Access"}}
repeat with i in aRes
  set aDom to theName of i
  
set accessNum to numberOfTimes of i
  
set the end of aList to {aDom, accessNum}
end repeat

–set aList to {{"Task", "Hours per Day"}, {"Work", 8}, {"Eat", 2}, {"TV", 4}, {"Gym", 2}, {"Sleep", 8}}

set aJsonArrayStr to array2DToJSONArray(aList) of me

–Pie Chart Template HTML
set myStr to "<!DOCTYPE html>
<html lang=\"UTF-8\">
<body>
<div id=\"piechart\"></div>

<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>

<script type=\"text/javascript\">
// Load google charts
google.charts.load(’current’, {’packages’:[’corechart’]});
google.charts.setOnLoadCallback(drawChart);

// Draw the chart and set the chart values
function drawChart() {
var data = google.visualization.arrayToDataTable(%@);

// Optional; add a title and set the width and height of the chart
var options = {’title’:’’, ’width’:600, ’height’:400};

// Display the chart inside the <div> element with id=\"piechart\"
var chart = new google.visualization.PieChart(document.getElementById(’piechart’));
chart.draw(data, options);
}
</script>

</body>
</html>"

set aString to current application’s NSString’s stringWithFormat_(myStr, aJsonArrayStr) as string

set paramObj to {myMessage:"Pie Chart Test", mySubMessage:"This is a simple pie chart using google charts", htmlStr:aString}
–my browseStrWebContents:paramObj–for debug
my performSelectorOnMainThread:"browseStrWebContents:" withObject:(paramObj) waitUntilDone:true

on browseStrWebContents:paramObj
  set aMainMes to myMessage of paramObj
  
set aSubMes to mySubMessage of paramObj
  
set htmlString to (htmlStr of paramObj)
  
  
set aWidth to 600
  
set aHeight to 450
  
  
–WebViewをつくる
  
set aConf to WKWebViewConfiguration’s alloc()’s init()
  
  
–指定HTML内のJavaScriptをFetch
  
set jsSource to pickUpFromToStr(htmlString, "<script type=\"text/javascript\">", "</script>") of me
  
  
set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true
  
set userContentController to WKUserContentController’s alloc()’s init()
  
userContentController’s addUserScript:(userScript)
  
aConf’s setUserContentController:userContentController
  
  
set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight – 100)) configuration:aConf
  
aWebView’s setNavigationDelegate:me
  
aWebView’s setUIDelegate:me
  
aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true
  
  
set bURL to |NSURL|’s fileURLWithPath:(POSIX path of (path to me))
  
aWebView’s loadHTMLString:htmlString baseURL:(bURL)
  
  
— set up alert  
  
set theAlert to NSAlert’s alloc()’s init()
  
tell theAlert
    its setMessageText:aMainMes
    
its setInformativeText:aSubMes
    
its addButtonWithTitle:"OK"
    
–its addButtonWithTitle:"Cancel"
    
its setAccessoryView:aWebView
    
    
set myWindow to its |window|
  end tell
  
  
— show alert in modal loop
  
NSRunningApplication’s currentApplication()’s activateWithOptions:0
  
my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true
  
  
–Stop Web View Action
  
set bURL to |NSURL|’s URLWithString:"about:blank"
  
set bReq to NSURLRequest’s requestWithURL:bURL
  
aWebView’s loadRequest:bReq
  
  
if (my returnCode as number) = 1001 then error number -128
end browseStrWebContents:

on doModal:aParam
  set (my returnCode) to (aParam’s runModal()) as number
end doModal:

on viewDidLoad:aNotification
  return true
end viewDidLoad:

on fetchJSSourceString(aURL)
  set jsURL to |NSURL|’s URLWithString:aURL
  
set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value)
  
return jsSourceString
end fetchJSSourceString

on pickUpFromToStr(aStr as string, s1Str as string, s2Str as string)
  set a1Offset to offset of s1Str in aStr
  
if a1Offset = 0 then return false
  
set bStr to text (a1Offset + (length of s1Str)) thru -1 of aStr
  
set a2Offset to offset of s2Str in bStr
  
if a2Offset = 0 then return false
  
set cStr to text 1 thru (a2Offset – (length of s2Str)) of bStr
  
return cStr as string
end pickUpFromToStr

–リストを任意のデリミタ付きでテキストに
on retArrowText(aList, aDelim)
  set aText to ""
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retArrowText

on array2DToJSONArray(aList)
  set anArray to current application’s NSMutableArray’s arrayWithArray:aList
  
set jsonData to current application’s NSJSONSerialization’s dataWithJSONObject:anArray options:(0 as integer) |error|:(missing value) –0 is
  
set resString to current application’s NSString’s alloc()’s initWithData:jsonData encoding:(current application’s NSUTF8StringEncoding)
  
return resString
end array2DToJSONArray

–Safariのn日前からの履歴をドメイン別に集計してソート
script safariHistoryLib
  use scripting additions
  
use framework "Foundation"
  
property parent : AppleScript
  
  
property |NSURL| : a reference to current application’s |NSURL|
  
  
script spd
    property sList : {}
    
property nList : {}
    
property sRes : {}
  end script
  
  
  
on calcSafariHistoryBest10Domain(daysBefore)
    –現在日時のn日前を求める
    
using terms from scripting additions
      set origDate to (current date) – (daysBefore * days)
    end using terms from
    
set dStr to convDateObjToStrWithFormat(origDate, "yyyy-MM-dd hh:mm:ss") of me
    
    
set aDBpath to "~/Library/Safari/History.db"
    
set pathString to current application’s NSString’s stringWithString:aDBpath
    
set newPath to pathString’s stringByExpandingTildeInPath()
    
    
set aText to "sqlite3 " & newPath & " ’SELECT datetime(history_visits.visit_time+978307200, \"unixepoch\", \"localtime\"), history_visits.title || \" @ \" || substr(history_items.URL,1,max(length(history_items.URL)*(instr(history_items.URL,\" & \")=0),instr(history_items.URL,\" & \"))) as Info FROM history_visits INNER JOIN history_items ON history_items.id = history_visits.history_item where history_visits.visit_time>(julianday(\"" & dStr & "\")*86400-211845068000) ORDER BY visit_time ASC LIMIT 999999;’"
    
    
using terms from scripting additions
      set (sRes of spd) to do shell script aText
    end using terms from
    
    
set (sList of spd) to (paragraphs of (sRes of spd))
    
    
set (nList of spd) to {}
    
    
repeat with i in (sList of spd)
      set j to contents of i
      
      
–Parse each field
      
set j2 to parseByDelim(j, {"|", "@ "}) of me
      
      
–Calculate URL’s domain only
      
set j3 to contents of last item of j2
      
set aURL to (|NSURL|’s URLWithString:j3)
      
      
if aURL = missing value then
        set j4 to ""
      else
        set j4 to (aURL’s |host|()) as string
      end if
      
      
set the end of (nList of spd) to j4
    end repeat
    
    
–登場頻度でURLを集計
    
set aList to countItemsByItsAppearance((nList of spd)) of me
    
    
    
–Best 10の計算のために10位以下をまとめる
    
set best9 to items 1 thru 9 of aList
    
set best10 to items 10 thru -1 of aList
    
    
set otherTimes to 0
    
repeat with i in best10
      set otherTimes to otherTimes + (numberOfTimes of i)
    end repeat
    
    
set the end of best9 to {theName:"Other", numberOfTimes:otherTimes}
    
return best9
  end calcSafariHistoryBest10Domain
  
  
  
  
on parseByDelim(aData, aDelim)
    set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to aDelim
    
set dList to text items of aData
    
set AppleScript’s text item delimiters to curDelim
    
return dList
  end parseByDelim
  
  
  
–出現回数で集計
  
on countItemsByItsAppearance(aList)
    set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
    
set bArray to current application’s NSMutableArray’s array()
    
set theEnumerator to aSet’s objectEnumerator()
    
    
repeat
      set aValue to theEnumerator’s nextObject()
      
if aValue is missing value then exit repeat
      
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{"theName", "numberOfTimes"})
    end repeat
    
    
–出現回数(numberOfTimes)で降順ソート
    
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:"numberOfTimes" ascending:false
    
bArray’s sortUsingDescriptors:{theDesc}
    
    
return bArray as list
  end countItemsByItsAppearance
  
  
  
on convDateObjToStrWithFormat(aDateO as date, aFormatStr as string)
    set aDF to current application’s NSDateFormatter’s alloc()’s init()
    
    
set aLoc to current application’s NSLocale’s currentLocale()
    
set aLocStr to (aLoc’s localeIdentifier()) as string
    
    
aDF’s setLocale:(current application’s NSLocale’s alloc()’s initWithLocaleIdentifier:aLocStr)
    
aDF’s setDateFormat:aFormatStr
    
set dRes to (aDF’s stringFromDate:aDateO) as string
    
return dRes
  end convDateObjToStrWithFormat
  
end script

★Click Here to Open This Script 

(Visited 50 times, 1 visits today)
Posted in dialog Web Contents Control | Tagged 10.13savvy 10.14savvy 10.15savvy NSAlert NSButton NSRunningApplication NSString NSURL NSURLRequest NSUTF8StringEncoding Safari WKUserContentController WKUserScript WKUserScriptInjectionTimeAtDocumentEnd WKWebView WKWebViewConfiguration | 1 Comment

指定文字列ではじまるURLをオープン中のTabをクローズ

Posted on 3月 1, 2020 by Takaaki Naganoya

Safariでオープン中のウィンドウ/Tabのうち、指定URLではじまるもの(この場合にはGoogle翻訳)をオープン中のものだけをクローズする掃除用のAppleScriptです。

TabのIDを取得して、1から処理するとナンバリングがおかしくなるので、後ろから処理しているあたりが「ワザ」とでもいうべきものでしょうか。

AppleScript名:指定文字列ではじまるURLをオープン中のTabをクローズ
—
–  Created by: Takaaki Naganoya
–  Created on: 2020/03/01
—
–  Copyright © 2020 Piyomaru Software, All Rights Reserved
—

set targURL to "https://translate.google.co.jp" –Google翻訳
closeSafariTabsBeginsWithAURL(targURL) of me

on closeSafariTabsBeginsWithAURL(targURL)
  tell application "Safari"
    set wCount to (every window whose visible is true)
    
    
repeat with w in wCount
      set aWin to contents of w
      
      
tell aWin
        set tabCount to count every tab
        
repeat with t from tabCount to 1 by -1
          tell tab t
            set aURL to URL of it
            
if aURL begins with targURL then
              close
            end if
          end tell
        end repeat
      end tell
      
    end repeat
  end tell
end closeSafariTabsBeginsWithAURL

★Click Here to Open This Script 

(Visited 63 times, 1 visits today)
Posted in URL | Tagged 10.13savvy 10.14savvy 10.15savvy Safari | Leave a comment

eppcで他のMac上のアプリケーション操作

Posted on 1月 26, 2020 by Takaaki Naganoya

MacScripterのフォーラムでeppcについての質問があったので回答していました。eppcを用いて、ネットワーク上の他のMacを操作する件についての話でした。

eppcは、Remote AppleEventのプロトコル表記であり、自分のマシン上のアプリケーションだと、

application "Finder"

と表記しますが、他のMac上のアプリケーションだと、

application "Finder" of machine "eppc://user:password@machineName.local"

という表記になります。パスワードを書いておかないとダイアログで問い合わせが行われるので、書かなくてもいいです(パスワードが丸裸で書いてあるのは不用心なので、テスト時以外はおすすめしません)。

自分の認識では、ネットワーク上の他のマシンの操作については、

のような認識でした。この認識は間違っているわけではなく、リモートのアプリケーション操作はリモート側のアプレットに行わせるようにして、自分からはリモート側のアプレットのハンドラを呼び出すのがリモートアプリケーション操作の基本的な「作法」です。

GUIアプリケーションを直接操作するのは無理だけど、AppleScriptアプレットを常駐させておいて、アプレットのハンドラを呼び出すことで、各リモートマシン上のアプリケーションを操作できる、と。

で、これが他のメンバーのコメントで(リモートアプリケーションのダイレクト呼び出しは)「macOS 10.15でもできるぞ」という話が出てきて、腰を抜かしました。現状の自分のマシン環境(古いのばっかりですが)で検証してみたところ、たしかに(若干の癖はありますが)、GUIアプリケーションを直接操作できて驚きました(セキュリティ上の制約がいろいろあるので、ローカルでできることすべてがリモートアプリケーションに直接司令できるわけではありません)。


▲テストに用いたMac OS X 10.6.8環境


▲テストに用いたMac OS X 10.7.5環境


▲テストに用いたMac OS X 10.13.6環境

Mac OS X 10.6からmacOS 10.12あたり(厳密には確認できていない)の環境では、明確にリモートAppleEventに制約が加わっていました。

ところが、macOS 10.13あたり(10.12かも。このあたり未確認)から、eppcでGUIアプリケーションの操作ができることが確認できました。

macOS Version Direct eppc to GUI Apps
10.0 (No AppleScript Env)
10.1 Works????
10.2 Works
10.3 Works
10.4 Works
10.5 (???)
10.6 Not Work
10.7 Not Work
10.8 Not Work
10.9 Not Work
10.1 Not Work
10.11 Not Work
10.12 (??? Maybe not work)
10.13 Works
10.14 Works
10.15 Works

eppcのポート

eppcがTCP/IPのどこのポート番号を用いているかは、/etc/servicesを見ると書いてあります。

eppc            3031/udp    # Remote AppleEvents/PPC Toolbox
eppc            3031/tcp    # Remote AppleEvents/PPC Toolbox

なので、VPN接続時にこれらのポートが開いていれば、相手側のマシンをAppleScriptで遠隔操作できることになります(実際には、ファイル共有とかいろいろその他の付随するポートを開けておく必要があるわけですが)。

リモート操作の傾向

ただし、ローカルマシン上のアプリケーションを操作するのと比較して、いくつかの挙動の違いも見られます。

(1)アプリケーションの直接的な起動が効かない

各GUIアプリケーションに「activate」とか「launch」コマンドを送っても無視されます。リモートマシン上でいったんアプリケーションが起動した後に「activate」で最前面に持ってくることは可能ですが、起動していない状態でactivateを実行しても、起動は行われません。

AppleScript名:他のマシン上でSafariを起動
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Finder"
  tell application "Finder" of machine RemoteMachine
    open application file id "com.apple.Safari"
  end tell
end using terms from

★Click Here to Open This Script 

Finder経由でBundle IDを指定して「application fileをオープンする」という指定であれば、たしかにアプリケーションが起動します。

(2)System Eventsの操作に難あり

面白くなっていろいろリモートアプリケーションを操作していてわかってきたのですが、初期状態だとSystem Eventsが起動していない状態です。いえ、ローカルでもSystem Eventsは常時起動してはいないんでしょうけれど、命令を発行すると自動で起動されます。

この自動起動とか明示的に指定して起動といった操作が受け付けられないので、Finder経由で間接的にapplication fileのオープンというかたちでSystem Eventsの起動を行い、処理を依頼します。

このリモートマシン上のSystem Eventsはしばらく処理が行われないと自動で終了するようなので、何かまとまった処理をSystem Eventsに行わせる前には明示的に起動を司令しておくべきなんでしょう。

AppleScript名:他のマシン上のSystem Eventsを起動する
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Finder"
  tell application "Finder" of machine RemoteMachine
    open application file id "com.apple.SystemEvents"
  end tell
end using terms from

★Click Here to Open This Script 

(3)けっこう動く

GUIアプリケーションの代表としてSafariを実際に操作してみたところ、

Safariのバージョン名の取得、できました。

AppleScript名:他のマシン上で起動しているアプリケーションのバージョンを取得
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Safari"
  tell application "Safari" of machine RemoteMachine
    version
  end tell
end using terms from

★Click Here to Open This Script 

Safariの新規ウィンドウの作成、できました。

AppleScript名:他のマシン上で起動しているSafariで新規ウィンドウ作成
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Safari"
  tell application "Safari" of machine RemoteMachine
    make new document
  end tell
end using terms from

★Click Here to Open This Script 

SafariのウィンドウのURLの取得、できました。

AppleScript名:他のマシン上で起動しているSafariの最前面のウィンドウのURLを取得
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Safari"
  tell application "Safari" of machine RemoteMachine
    set aURL to URL of front document
  end tell
end using terms from

★Click Here to Open This Script 

SafariのウィンドウのURLの書き換え、できました。

AppleScript名:他のマシン上で起動しているSafariのウィンドウのURLを書き換える
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Safari"
  tell application "Safari" of machine RemoteMachine
    set URL of document 1 to "http://piyocast.com/as/"
  end tell
end using terms from

★Click Here to Open This Script 

Safariの最前面のウィンドウの内容をメールに転送、できました。

AppleScript名:他のマシン上で起動しているSafariの最前面の内容をメール
set RemoteMachine to "eppc://me@MacMini2014.local"

using terms from application "Safari"
  tell application "Safari" of machine RemoteMachine
    make new document
    
set URL of front document to "http://www.apple.com"
    
delay 3 –wait for page loading
    
email contents of front document
  end tell
end using terms from

★Click Here to Open This Script 

Safariのページローディング検出はいろいろ蓄積されているノウハウもありますが、do javascriptコマンドが問題なく動くレベルまで信用できるのかわからなかったので、delayで単純に時間待ちしています。

実際に動かしてみて、自分がビビるぐらいeppcでリモートアプリケーションの操作ができてしまいました。SIPだとかアプリケーション権限だとかでセキュリティ機能でがんじがらめにされている今日このごろですが、eppc経由でリモートマシン上のアプリケーション操作が緩和されている事実にビビりました。

これはおそらくですが、Xcode上のリモートデバッグとか、そういうあたりの機能の実装時に機能がeppcまわりの機能が再実装されたかコメントアウトしていた箇所が復活したかという話に見えます。

(Visited 249 times, 1 visits today)
Posted in Remote Control | Tagged 10.13savvy 10.14savvy 10.15savvy Safari | Leave a comment

Safariで現在見えている表を抽出してCSV書き出しv3

Posted on 10月 8, 2019 by Takaaki Naganoya

Safariの最前面のウィンドウで表示中のページのうち、現在ウィンドウ内に表示中の表要素をCSV書き出ししてNumbersでオープンするAppleScriptの改良版です。HTMLのtable中にrowspan(複数セルを行方向に連結)とcolspan(複数セルを列方向に連結)の属性値が指定されていた場合に対応します。

–> Download table2CSV_visibleonly_v2 (Code-Signed AppleScript applet with Framework and Library in its bundle)

各DOM ElementsのWebコンテンツ中の表示座標を取得して、絞り込みを行なっています。ただし、各DOM座標はWebブラウザのスクロールにしたがって数値が変わる(相対座標)ため、少々手こずりました。また、本Scriptでは上下スクロールのみ考慮してDOM要素の抽出を行なっており、横に長いページの横方向スクロールは考慮しておりません。

このバージョンではrowspan / colspanへの対処を追加しました。

行単位で(1次元配列ベースで)表を作っていてはとても対処できなかったので、HTMLの表と同じセル数のヌル文字が入った2次元配列を作成し、そこにX座標/Y座標を指定してセルを埋めるように処理内容を変更しました。また、rowspan/colspanの属性を見つけた場合には、結合されていた複数セルを個別の(同じ値を入れた)セルに分解しています。

本バージョンでは、1つのセル(td)でrowspanとcolspanを同時に指定しないことが処理の前提条件となっています。また、一番上の行がヘッダーの場合を想定しており、一番左の列がヘッダーになっているケースには対処しておりません。

AppleScript名:Safariで現在見えている表を抽出してCSV書き出しv3.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2019/09/22
–  Modified on: 2019/10/07
—
–  Copyright © 2019 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "HTMLReader" –https://github.com/nolanw/HTMLReader
use aLib : script "arrayLib"

property NSUUID : a reference to current application’s NSUUID
property NSString : a reference to current application’s NSString
property HTMLDocument : a reference to current application’s HTMLDocument
property NSMutableArray : a reference to current application’s NSMutableArray
property NSJSONSerialization : a reference to current application’s NSJSONSerialization

set aTag to "table"

set indRes to getVisibleElementIndexList(aTag) of me
if indRes = false or indRes = {} then
  display notification "No Visible Table in Web browser"
  
return
end if

tell application "Safari"
  tell front document
    set aSource to source
  end tell
end tell

repeat with i in indRes
  set inList to filterATableAndPaseCells(aSource, i, aTag) of me
  
  
if inList = false or inList = {} then return
  
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
  
set aNewFile to ((path to desktop) as string) & aUUID & ".csv"
  
saveAsCSV(inList, aNewFile) of me
  
  
tell application "Numbers"
    activate
    
open (aNewFile as alias)
  end tell
end repeat

on filterATableAndPaseCells(aSource as string, targInd as integer, aTag as string)
  set aHTML to current application’s HTMLDocument’s documentWithString:(aSource as string)
  
  
–Table要素をリストアップ
  
set eList to (aHTML’s nodesMatchingSelector:aTag) as list
  
set aObj to contents of item (targInd + 1) of eList
  
  
–Count columns of Table Header (Count only)
  
set aTableHeader to (aObj’s nodesMatchingSelector:"tr")’s firstObject()
  
set hList to aTableHeader’s nodesMatchingSelector:"th"
  
set hStrList to {}
  
repeat with i1 in hList
    set hCellStr to i1’s textContent() as string
    
set the end of hStrList to (hCellStr)
  end repeat
  
set hLen to length of hStrList –count columns
  
  
  
–Acquire whole table body contents
  
set aTableBody to (aObj’s nodesMatchingSelector:"tbody")’s firstObject()
  
set bList to (aTableBody’s nodesMatchingSelector:"tr") as list
  
  
set rCount to (length of bList) –count rows
  
  
–行単位ループ
  
set yCount to 1
  
set attrList to make2DBlankArray(hLen, rCount) of aLib
  
  
repeat with i2 in bList
    set bb2List to {}
    
set i3 to (i2’s nodesMatchingSelector:"th") as list
    
if i3 = {} then
      set i3 to (i2’s nodesMatchingSelector:"td") as list
    end if
    
    
–カラム単位ループ
    
set xCount to 1
    
repeat with i4 in i3
      set anAttr to i4’s attributes()
      
set colAtr to (anAttr’s valueForKey:"colspan")
      
set rowAttr to (anAttr’s valueForKey:"rowspan")
      
set cellStr to i4’s textContent() as string
      
      
if colAtr is not equal to missing value then
        –colspan処理
        
set colNum to colAtr as integer
        
set attrList to xFill(xCount, yCount, attrList, cellStr, colNum) of aLib
        
      else if rowAttr is not equal to missing value then
        –rowspan処理
        
set rowNum to rowAttr as integer
        
set attrList to yFill(xCount, yCount, attrList, cellStr, rowNum) of aLib
        
      else if cellStr is not equal to "" then
        –通常処理
        
repeat with ii from xCount to hLen
          set aRes to getItemByXY(ii, yCount, attrList, "") of aLib
          
if aRes = "" then
            set attrList to setItemByXY(ii, yCount, attrList, cellStr) of aLib
            
exit repeat
          else
            set xCount to xCount + 1
          end if
        end repeat
        
      end if
      
      
set xCount to xCount + 1
    end repeat
    
    
set yCount to yCount + 1
  end repeat
  
  
return attrList
end filterATableAndPaseCells

–Safariのウィンドウ上で表示中のDOM Elementsを座標計算して返す
on getVisibleElementIndexList(aTag as string)
  tell application "Safari"
    set dCount to count every document
    
if dCount = 0 then return false
    
    
set jRes to do JavaScript "var winWidth = window.innerWidth,
winHeight = window.innerHeight,
winLeft = window.scrollX,
winTop = window.scrollY,
winBottom = winTop + winHeight,
winRight = winLeft + winWidth,
    elementsArray = document.body.getElementsByTagName(’" & aTag & "’),
    elemLen = elementsArray.length,
inView = [];
      
    var step;
    for (step = 0 ; step < elemLen ; step++) {
      var tmpElem = document.body.getElementsByTagName(’" & aTag & "’)[step];
      var bVar = tmpElem.getBoundingClientRect();
      if (bVar.top > 0 && bVar.top < winHeight) {
        inView.push(step);
      }
    }
    JSON.stringify(inView);"
in front document
    
    
set jList to parseJSONAsList(jRes) of me
    
return jList
    
  end tell
end getVisibleElementIndexList

on parseJSONAsList(jsRes as string)
  set jsonString to NSString’s stringWithString:jsRes
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
return aJsonDict as list
end parseJSONAsList

–Save 2D List to CSV file
on saveAsCSV(aList as list, aPath)
  set crlfChar to (string id 13) & (string id 10)
  
set LF to (string id 10)
  
set wholeText to ""
  
  
repeat with i in aList
    set newLine to {}
    
    
–Sanitize (Double Quote)
    
repeat with ii in i
      set jj to ii as text
      
set kk to repChar(jj, string id 34, (string id 34) & (string id 34)) of me –Escape Double Quote
      
set the end of newLine to kk
    end repeat
    
    
–Change Delimiter
    
set aLineText to ""
    
set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to "\",\""
    
set aLineList to newLine as text
    
set AppleScript’s text item delimiters to curDelim
    
    
set aLineText to repChar(aLineList, return, "") of me –delete return
    
set aLineText to repChar(aLineText, LF, "") of me –delete lf
    
    
set wholeText to wholeText & "\"" & aLineText & "\"" & crlfChar –line terminator: CR+LF
  end repeat
  
  
if (aPath as string) does not end with ".csv" then
    set bPath to aPath & ".csv" as Unicode text
  else
    set bPath to aPath as Unicode text
  end if
  
  
writeToFileAsUTF8(wholeText, bPath, false) of me
  
end saveAsCSV

on writeToFileAsUTF8(this_data, target_file, append_data)
  tell current application
    try
      set the target_file to the target_file as text
      
set the open_target_file to open for access file target_file with write permission
      
if append_data is false then set eof of the open_target_file to 0
      
write this_data as «class utf8» to the open_target_file starting at eof
      
close access the open_target_file
      
return true
    on error error_message
      try
        close access file target_file
      end try
      
return error_message
    end try
  end tell
end writeToFileAsUTF8

on repChar(origText as text, targChar as text, repChar as text)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to targChar
  
set tmpList to text items of origText
  
set AppleScript’s text item delimiters to repChar
  
set retText to tmpList as string
  
set AppleScript’s text item delimiters to curDelim
  
return retText
end repChar

★Click Here to Open This Script 

(Visited 141 times, 1 visits today)
Posted in file JavaScript JSON list Record Text | Tagged 10.12savvy 10.13savvy 10.14savvy HTMLDocument NSJSONSerialization NSMutableArray NSString NSUUID Numbers Safari | 4 Comments

Safariで現在見えている表を抽出してCSV書き出し

Posted on 9月 24, 2019 by Takaaki Naganoya

Safariの最前面のウィンドウで表示中のページのうち、現在ウィンドウ内に表示中の表要素をCSV書き出ししてNumbersでオープンするAppleScriptです。

このところ下調べを行なっていた「Webブラウザで表示中の要素を処理する」「表示中ではない要素は処理をしない」というScriptです。

これで、「表の一部を選択しておく」とかいった操作は不要になりました。ウィンドウ内に表示されている表をWebコンテンツ内部の表示座標をもとに自動抽出します。表示エリア外に位置しているものは書き出し処理しません。

各DOM ElementsのWebコンテンツ中の表示座標を取得して、絞り込みを行なっています。ただし、各DOM座標はWebブラウザのスクロールにしたがって数値が変わる(相対座標)ため、少々手こずりました。また、本Scriptでは上下スクロールのみ考慮してDOM要素の抽出を行なっており、横に長いページの横方向スクロールは考慮しておりません。

本Scriptは大量一括処理を志向するプログラムではなく、「見えているもの」をそのまま処理してほしいという考えで作ったものでもあり、Webブラウザ(Safari)で表示中のページのソースを取得してそのまま処理しています。つまり、ユーザーが閲覧中のページのデータそのものを処理しています。

これは、ページのソースを取得するコマンドを持っていないGoogle Chromeにはできない処理です(同じURLの内容を別途curlコマンドなどで取得すればOK。Cookie値などの再現が大変でしょうけれども)。

その他、実際に作って使ってみた感想は、装飾用に使われている表データまで取り込んでしまう点に不満があるぐらいでしょうか。これら「ゴミデータ」(再利用する価値のない装飾用の表データ)を区別するために、行数が足りない場合には書き出さないといった「足切り」を行う必要性を感じます。

–> Download VisibleTableExporter(Code-signed executable applet with Framework in its bundle)

AppleScript名:Safariで現在見えている表を抽出してCSV書き出し.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2019/09/22
—
–  Copyright © 2019 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "HTMLReader" –https://github.com/nolanw/HTMLReader

property NSUUID : a reference to current application’s NSUUID
property NSString : a reference to current application’s NSString
property HTMLDocument : a reference to current application’s HTMLDocument
property NSMutableArray : a reference to current application’s NSMutableArray
property NSJSONSerialization : a reference to current application’s NSJSONSerialization

set aTag to "table"

set indRes to getVisibleElementIndexList(aTag) of me
if indRes = false or indRes = {} then
  display notification "No Visible Table in Web browser"
  
return
end if

tell application "Safari"
  tell front document
    set aSource to source
  end tell
end tell

repeat with i in indRes
  set inList to filterATableAndPaseCells(aSource, i, aTag) of me
  
if inList = false or inList = {} then return
  
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
  
set aNewFile to ((path to desktop) as string) & aUUID & ".csv"
  
saveAsCSV(inList, aNewFile) of me
  
  
tell application "Numbers"
    open (aNewFile as alias)
  end tell
end repeat

tell application "Numbers" to activate

on filterATableAndPaseCells(aSource as string, targInd as integer, aTag as string)
  set aHTML to current application’s HTMLDocument’s documentWithString:(aSource as string)
  
  
–Table要素をリストアップ
  
set eList to (aHTML’s nodesMatchingSelector:aTag) as list
  
set aObj to contents of item (targInd + 1) of eList
  
  
  
–Count columns of Table Header
  
set aTableHeader to (aObj’s nodesMatchingSelector:"tr")’s firstObject()
  
set hList to aTableHeader’s nodesMatchingSelector:"th"
  
set hStrList to {}
  
repeat with i1 in hList
    set the end of hStrList to i1’s textContent() as string
  end repeat
  
set hLen to length of hStrList –count columns
  
  
–Acquire whole table body contents
  
set aTableBody to (aObj’s nodesMatchingSelector:"tbody")’s firstObject()
  
set bList to aTableBody’s nodesMatchingSelector:"td"
  
set bbList to {}
  
repeat with i2 in bList
    set the end of bbList to i2’s textContent() as string
  end repeat
  
  
set tbList to makeList1DTo2D(bbList, hLen) of me
  
  
return {hStrList} & tbList
end filterATableAndPaseCells

–1D Listを2D化
on makeList1DTo2D(orig1DList as list, aMax)
  set tbList to {}
  
set tmpList to {}
  
set aCount to 1
  
  
repeat with i3 in orig1DList
    set j to contents of i3
    
set the end of tmpList to j
    
    
if aCount ≥ aMax then
      set aCount to 1
      
set the end of tbList to tmpList
      
set tmpList to {}
    else
      set aCount to aCount + 1
    end if
  end repeat
  
  
return tbList
end makeList1DTo2D

–Safariのウィンドウ上で表示中のDOM Elementsを座標計算して返す
on getVisibleElementIndexList(aTag as string)
  tell application "Safari"
    set dCount to count every document
    
if dCount = 0 then return false
    
    
set jRes to do JavaScript "var winWidth = window.innerWidth,
winHeight = window.innerHeight,
winLeft = window.scrollX
winTop = window.scrollY,
winBottom = winTop + winHeight,
winRight = winLeft + winWidth,
    elementsArray = document.body.getElementsByTagName(’" & aTag & "’),
    elemLen = elementsArray.length,
inView = [];
      
    var step;
    for (step = 0 ; step < elemLen ; step++) {
      var tmpElem = document.body.getElementsByTagName(’" & aTag & "’)[step];
      var bVar = tmpElem.getBoundingClientRect();
      if (bVar.top > 0 && bVar.top < winHeight) {
        inView.push(step);
      }
    }
    JSON.stringify(inView);"
in front document
    
    
set jList to parseJSONAsList(jRes) of me
    
return jList
    
  end tell
end getVisibleElementIndexList

on parseJSONAsList(jsRes as string)
  set jsonString to NSString’s stringWithString:jsRes
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
return aJsonDict as list
end parseJSONAsList

–Save 2D List to CSV file
on saveAsCSV(aList as list, aPath)
  set crlfChar to (string id 13) & (string id 10)
  
set LF to (string id 10)
  
set wholeText to ""
  
  
repeat with i in aList
    set newLine to {}
    
    
–Sanitize (Double Quote)
    
repeat with ii in i
      set jj to ii as text
      
set kk to repChar(jj, string id 34, (string id 34) & (string id 34)) of me –Escape Double Quote
      
set the end of newLine to kk
    end repeat
    
    
–Change Delimiter
    
set aLineText to ""
    
set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to "\",\""
    
set aLineList to newLine as text
    
set AppleScript’s text item delimiters to curDelim
    
    
set aLineText to repChar(aLineList, return, "") of me –delete return
    
set aLineText to repChar(aLineText, LF, "") of me –delete lf
    
    
set wholeText to wholeText & "\"" & aLineText & "\"" & crlfChar –line terminator: CR+LF
  end repeat
  
  
if (aPath as string) does not end with ".csv" then
    set bPath to aPath & ".csv" as Unicode text
  else
    set bPath to aPath as Unicode text
  end if
  
  
writeToFileAsUTF8(wholeText, bPath, false) of me
  
end saveAsCSV

on writeToFileAsUTF8(this_data, target_file, append_data)
  tell current application
    try
      set the target_file to the target_file as text
      
set the open_target_file to open for access file target_file with write permission
      
if append_data is false then set eof of the open_target_file to 0
      
write this_data as «class utf8» to the open_target_file starting at eof
      
close access the open_target_file
      
return true
    on error error_message
      try
        close access file target_file
      end try
      
return error_message
    end try
  end tell
end writeToFileAsUTF8

on repChar(origText as text, targChar as text, repChar as text)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to targChar
  
set tmpList to text items of origText
  
set AppleScript’s text item delimiters to repChar
  
set retText to tmpList as string
  
set AppleScript’s text item delimiters to curDelim
  
return retText
end repChar

★Click Here to Open This Script 

(Visited 147 times, 4 visits today)
Posted in file JavaScript JSON list Text | Tagged 10.12savvy 10.13savvy 10.14savvy HTMLDocument NSJSONSerialization NSMutableArray NSString NSUUID Numbers Safari | 1 Comment

Safariで指定のDOM Elementの情報を取得する

Posted on 9月 22, 2019 by Takaaki Naganoya

Safariで表示中の最前面のDocument内の指定Tag、指定Index(出現順、0はじまり)のDOM Elementの詳細なプロパティ値を取得するAppleScriptです。

複数のTableが存在しているページのそれぞれのプロパティ値を取得してdiffを取るための調査用Scriptです。

AppleScript名:Safariで指定のDOM Elementの情報を取得する
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

tell application "Safari"
  set jsStr to "var elementsArray = document.body.getElementsByTagName(’table’);
    aVar = window.getComputedStyle(elementsArray[1]);
  JSON.stringify(aVar);"
  
  
set aHeight to do JavaScript jsStr in front document
  
set cRes to parseJSONAsRecord(aHeight) of me
end tell

–> {|95|:"font-style", |50|:"caret-color", |96|:"font-synthesis", |51|:"clear", |97|:"font-variant", webkitMaskPosition:"0% 0%", |52|:"clip", |98|:"font-variant-alternates", webkitHyphens:"manual", |right|:"auto", |53|:"clip-path", |99|:"font-variant-caps", |54|:"clip-rule", backgroundAttachment:"scroll", willChange:"auto", |55|:"color", margin:"0px 0px 20px", marginTop:"0px", |10|:"animation-name", |56|:"color-interpolation", webkitColumnBreakBefore:"auto", baselineShift:"baseline", webkitLineSnap:"none", |11|:"animation-play-state", |57|:"color-interpolation-filters", scrollSnapMargin:"0px", transform:"none", |12|:"animation-timing-function", |58|:"color-rendering", |13|:"background-attachment", |59|:"color-scheme", fontVariantPosition:"normal", gridAutoFlow:"row", |14|:"background-blend-mode", position:"static", |15|:"background-clip", |16|:"background-color", |17|:"background-image", webkitMaskComposite:"source-over", |18|:"background-origin", justifyItems:"normal", |19|:"background-position", colorRendering:"auto", |color|:"rgb(51, 51, 51)", listStyleImage:"none", borderBottom:"0px none rgb(128, 128, 128)", backgroundOrigin:"padding-box", counterIncrement:"none", webkitMaskRepeatY:"", writingMode:"horizontal-tb", borderInlineEndStyle:"none", columnGap:"normal", textDecorationLine:"none", scrollSnapMarginBottom:"0px", strokeColor:"rgba(0, 0, 0, 0)", webkitBoxOrdinalGroup:"1", alignItems:"normal", caretColor:"rgb(51, 51, 51)", webkitMarginBottomCollapse:"collapse", paddingLeft:"0px", fontWeight:"normal", padding:"0px", scrollPaddingTop:"0px", flexGrow:"0", boxSizing:"border-box", webkitBackgroundSize:"auto", marginBottom:"20px", gridTemplateRows:"none", background:"rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box", fontDisplay:"", speakAs:"normal", |left|:"auto", webkitBoxDecorationBreak:"slice", scrollSnapAlign:"none none", backgroundPosition:"0% 0%", gridRowStart:"auto", borderBlockStartWidth:"0px", borderTopLeftRadius:"0px", webkitUserDrag:"auto", borderRightColor:"rgb(128, 128, 128)", fontVariantAlternates:"normal", breakInside:"auto", webkitTextZoom:"normal", marker:"", webkitClipPath:"none", strokeLinejoin:"miter", webkitMarquee:"", breakAfter:"auto", webkitMaskPositionX:"0%", whiteSpace:"normal", backgroundColor:"rgba(0, 0, 0, 0)", flexDirection:"row", columns:"auto auto", transformOriginX:"", clip:"auto", emptyCells:"show", transformOrigin:"413.75px 309.4021911621094px", borderLeftColor:"rgb(128, 128, 128)", flex:"0 1 auto", webkitTextStroke:"", clipRule:"nonzero", grid:"none / none / none / row / auto / auto", listStyleType:"disc", maxWidth:"none", borderInlineStartStyle:"none", webkitTextOrientation:"mixed", clipPath:"none", justifySelf:"auto", clear:"none", transformOriginY:"", overflowWrap:"break-word", webkitHyphenateLimitAfter:"auto", tabSize:"8", marginBlockStart:"0px", |120|:"image-rendering", unicodeRange:"", gridColumnEnd:"auto", webkitRubyPosition:"before", |220|:"text-rendering", animationDirection:"normal", webkitMaskOrigin:"border-box", |121|:"isolation", fontStyle:"normal", |320|:"-webkit-text-emphasis-position", |221|:"text-shadow", webkitTextFillColor:"rgb(51, 51, 51)", |122|:"justify-content", borderBottomLeftRadius:"0px", |321|:"-webkit-text-emphasis-style", |80|:"fill-rule", |222|:"text-transform", |123|:"justify-items", |81|:"filter", |322|:"-webkit-text-fill-color", transformOriginZ:"", borderRightWidth:"0px", |82|:"flex-basis", |223|:"text-underline-position", |124|:"justify-self", webkitTextSizeAdjust:"auto", |83|:"flex-direction", |323|:"-webkit-text-orientation", |224|:"top", |84|:"flex-flow", |125|:"kerning", |150|:"opacity", |85|:"flex-grow", |324|:"-webkit-text-security", borderBlockStartStyle:"none", |225|:"touch-action", |40|:"border-top-color", |86|:"flex-shrink", |126|:"left", |250|:"-apple-color-filter", |151|:"order", |41|:"border-top-left-radius", |87|:"flex-wrap", |325|:"-webkit-text-size-adjust", |226|:"transform", border:"0px none rgb(128, 128, 128)", |42|:"border-top-right-radius", |88|:"float", |127|:"letter-spacing", |152|:"orphans", |251|:"-webkit-appearance", |43|:"border-top-style", |89|:"flood-color", |227|:"transform-box", |326|:"-webkit-text-stroke-color", |252|:"-webkit-backdrop-filter", |44|:"border-top-width", |128|:"lighting-color", |153|:"outline-color", |327|:"-webkit-text-stroke-width", borderBlockEndColor:"rgb(128, 128, 128)", |45|:"bottom", |228|:"transform-origin", borderInlineEnd:"0px none rgb(128, 128, 128)", |253|:"-webkit-backface-visibility", |129|:"line-break", |46|:"box-shadow", |154|:"outline-offset", |328|:"-webkit-text-zoom", alignContent:"normal", borderLeftStyle:"none", |47|:"box-sizing", |229|:"transform-style", |254|:"-webkit-background-clip", colorScheme:"auto", shapeOutside:"none", |48|:"buffered-rendering", |155|:"outline-style", |180|:"ry", |329|:"-webkit-transform-style", minBlockSize:"0px", |49|:"caption-side", |255|:"-webkit-background-composite", inlineSize:"827.5px", |280|:"-webkit-font-kerning", |156|:"outline-width", textDecorationColor:"rgb(51, 51, 51)", |181|:"scroll-padding", transitionProperty:"all", webkitBoxDirection:"normal", webkitInitialLetter:"normal", |256|:"-webkit-background-origin", gridColumn:"auto / auto", |281|:"-webkit-font-smoothing", |157|:"overflow-wrap", listStyle:"disc outside none", |182|:"scroll-padding-bottom", webkitMaskBoxImage:"none", floodColor:"rgb(0, 0, 0)", webkitTextEmphasisPosition:"over right", |257|:"-webkit-background-size", scrollSnapMarginRight:"0px", |282|:"-webkit-hyphenate-character", |158|:"overflow-x", webkitTextSecurity:"none", |183|:"scroll-padding-left", columnRule:"0px none rgb(51, 51, 51)", |258|:"-webkit-border-fit", gridColumnStart:"auto", |283|:"-webkit-hyphenate-limit-after", |159|:"overflow-y", touchAction:"auto", |184|:"scroll-padding-right", webkitFontSizeDelta:"", webkitLocale:"ja", |259|:"-webkit-border-horizontal-spacing", |284|:"-webkit-hyphenate-limit-before", colorInterpolation:"sRGB", borderTopColor:"rgb(128, 128, 128)", |185|:"scroll-padding-top", |285|:"-webkit-hyphenate-limit-lines", hangingPunctuation:"none", |186|:"scroll-snap-align", mask:"none", borderRight:"0px none rgb(128, 128, 128)", paddingInlineStart:"0px", webkitBackgroundOrigin:"padding-box", |286|:"-webkit-hyphens", |187|:"scroll-snap-margin", pageBreakAfter:"auto", colorProfile:"", textAnchor:"start", |287|:"-webkit-initial-letter", webkitMaskSize:"auto", |188|:"scroll-snap-margin-bottom", overflowX:"visible", |288|:"-webkit-line-align", strokeLinecap:"butt", |189|:"scroll-snap-margin-left", |289|:"-webkit-line-box-contain", borderBottomColor:"rgb(128, 128, 128)", paddingRight:"0px", fontOpticalSizing:"auto", borderImage:"none", floodOpacity:"1", webkitAspectRatio:"auto", webkitColumnProgression:"normal", alignSelf:"auto", display:"table", borderRadius:"0px", maxInlineSize:"none", minHeight:"0px", strokeMiterlimit:"4", webkitBorderVerticalSpacing:"0px", webkitMaskClip:"border-box", webkitBorderHorizontalSpacing:"0px", objectPosition:"50% 50%", kerning:"0", borderStyle:"none", visibility:"visible", textShadow:"none", borderLeftWidth:"0px", shapeImageThreshold:"0", scrollSnapMarginTop:"0px", markerMid:"none", scrollPadding:"0px", markerEnd:"none", webkitFontKerning:"auto", borderBottomWidth:"0px", float:"none", placeSelf:"auto auto", webkitBackgroundComposite:"source-over", borderTop:"0px none rgb(128, 128, 128)", backgroundRepeat:"repeat", webkitMarqueeDirection:"auto", animationIterationCount:"1", backgroundPositionX:"0%", content:"", transformStyle:"flat", borderInlineEndColor:"rgb(128, 128, 128)", borderBlockStart:"0px none rgb(128, 128, 128)", overflow:"visible", webkitMaskPositionY:"0%", perspective:"none", strokeDashoffset:"0px", outlineColor:"rgb(51, 51, 51)", webkitMarqueeSpeed:"", webkitTextEmphasisStyle:"none", isolation:"auto", borderInlineStartColor:"rgb(128, 128, 128)", textDecorationSkip:"auto", outlineOffset:"0px", objectFit:"fill", textUnderlineOffset:"auto", webkitAppearance:"none", webkitMaskBoxImageWidth:"auto", animationTimingFunction:"ease", borderWidth:"0px", borderSpacing:"0px 0px", captionSide:"top", columnWidth:"auto", rowGap:"normal", webkitMaskBoxImageSource:"none", webkitRtlOrdering:"logical", |70|:"counter-reset", paddingInlineEnd:"0px", |71|:"cursor", |72|:"cx", fontSize:"14.000000953674316px", |73|:"cy", vectorEffect:"none", |74|:"direction", justifyContent:"normal", fillRule:"nonzero", |75|:"display", fontSynthesis:"style weight small-caps", |30|:"border-image-repeat", |76|:"dominant-baseline", |31|:"border-image-slice", |77|:"empty-cells", pageBreakBefore:"auto", |32|:"border-image-source", |78|:"fill", cx:"0px", webkitMaskBoxImageRepeat:"stretch", borderImageSource:"none", |33|:"border-image-width", |79|:"fill-opacity", cy:"0px", outlineStyle:"none", webkitBoxFlex:"0", |34|:"border-left-color", width:"827.5px", webkitMarginBeforeCollapse:"collapse", |35|:"border-left-style", borderRightStyle:"none", |36|:"border-left-width", |0|:"align-content", |37|:"border-right-color", |1|:"align-items", paddingBlockStart:"0px", |size|:"", |2|:"align-self", |38|:"border-right-style", |3|:"alignment-baseline", |4|:"alt", |39|:"border-right-width", webkitFontSmoothing:"auto", |5|:"animation-delay", borderTopWidth:"0px", |6|:"animation-direction", |100|:"font-variant-east-asian", |7|:"animation-duration", |8|:"animation-fill-mode", |200|:"stroke", |9|:"animation-iteration-count", webkitTransformStyle:"flat", |101|:"font-variant-ligatures", counterReset:"none", |300|:"-webkit-mask-box-image", maskType:"luminance", webkitBoxShadow:"none", |201|:"stroke-color", borderBlockEndStyle:"none", |102|:"font-variant-numeric", fontStretch:"normal", textDecorationStyle:"solid", |301|:"-webkit-mask-box-image-outset", cursor:"auto", |202|:"stroke-dasharray", webkitMarqueeStyle:"scroll", |103|:"font-variant-position", animationFillMode:"none", glyphOrientationVertical:"auto", |302|:"-webkit-mask-box-image-repeat", |203|:"stroke-dashoffset", textRendering:"auto", |104|:"font-variation-settings", borderImageRepeat:"stretch", |303|:"-webkit-mask-box-image-slice", |204|:"stroke-linecap", height:"618.8043823242188px", |105|:"font-weight", all:"", |130|:"line-height", |304|:"-webkit-mask-box-image-source", pointerEvents:"auto", textTransform:"none", |205|:"stroke-linejoin", dominantBaseline:"auto", |230|:"transition-delay", |106|:"glyph-orientation-horizontal", filter:"none", |131|:"list-style-image", |305|:"-webkit-mask-box-image-width", |330|:"-webkit-user-drag", |206|:"stroke-miterlimit", paintOrder:"normal", |231|:"transition-duration", |107|:"glyph-orientation-vertical", placeItems:"normal normal", |132|:"list-style-position", |306|:"-webkit-mask-clip", borderInlineStart:"0px none rgb(128, 128, 128)", |331|:"-webkit-user-modify", |207|:"stroke-opacity", textOverflow:"clip", |232|:"transition-property", |108|:"grid-auto-columns", webkitBackdropFilter:"none", |133|:"list-style-type", |307|:"-webkit-mask-composite", webkitMaskRepeat:"repeat", |332|:"-webkit-user-select", |208|:"stroke-width", webkitBoxReflect:"none", |233|:"transition-timing-function", |109|:"grid-auto-flow", |134|:"margin-bottom", |308|:"-webkit-mask-image", |333|:"fullscreen-inset-bottom", |209|:"tab-size", |234|:"unicode-bidi", flexWrap:"nowrap", wordSpacing:"0px", |135|:"margin-left", |309|:"-webkit-mask-origin", |160|:"padding-bottom", |334|:"safe-area-inset-bottom", flexFlow:"row nowrap", |235|:"vector-effect", |260|:"-webkit-border-image", |136|:"margin-right", |161|:"padding-left", |335|:"safe-area-inset-left", transition:"all 0s ease 0s", |236|:"vertical-align", columnFill:"balance", |261|:"-webkit-border-vertical-spacing", |137|:"margin-top", |162|:"padding-right", |336|:"fullscreen-inset-left", maxHeight:"none", |237|:"visibility", alt:"\"\"", |262|:"-webkit-box-align", |138|:"marker-end", marginInlineEnd:"0px", |163|:"padding-top", |337|:"fullscreen-inset-right", webkitMarginTopCollapse:"collapse", |238|:"white-space", gridRowEnd:"auto", |263|:"-webkit-box-decoration-break", |139|:"marker-mid", |164|:"page-break-after", |338|:"fullscreen-auto-hide-duration", fillOpacity:"1", |239|:"widows", |264|:"-webkit-box-direction", overflowY:"visible", |165|:"page-break-before", |339|:"safe-area-inset-right", |190|:"scroll-snap-margin-right", |265|:"-webkit-box-flex", animationDuration:"0s", |290|:"-webkit-line-clamp", |166|:"page-break-inside", marginRight:"0px", |191|:"scroll-snap-margin-top", |266|:"-webkit-box-flex-group", borderInlineStartWidth:"0px", |291|:"-webkit-line-grid", |167|:"paint-order", webkitNbspMode:"normal", |192|:"scroll-snap-type", |267|:"-webkit-box-lines", mixBlendMode:"normal", |292|:"-webkit-line-snap", |168|:"perspective", paddingBlockEnd:"0px", |193|:"shape-image-threshold", src:"", webkitBoxAlign:"stretch", |268|:"-webkit-box-ordinal-group", paddingTop:"0px", |293|:"-webkit-locale", |169|:"perspective-origin", borderBlockEnd:"0px none rgb(128, 128, 128)", |194|:"shape-margin", r:"0px", textAlign:"start", |269|:"-webkit-box-orient", webkitMaskBoxImageOutset:"0px", |294|:"-webkit-margin-after-collapse", webkitTextEmphasis:"", webkitTextStrokeColor:"rgb(51, 51, 51)", |195|:"shape-outside", breakBefore:"auto", wordWrap:"break-word", |295|:"-webkit-margin-before-collapse", minInlineSize:"0px", |196|:"shape-rendering", borderInlineEndWidth:"0px", webkitLineAlign:"none", webkitMaskBoxImageSlice:"0 fill", widows:"auto", perspectiveOrigin:"413.75px 309.4021911621094px", |296|:"-webkit-marquee-direction", x:"0px", y:"0px", |197|:"speak-as", perspectiveOriginX:"", |297|:"-webkit-marquee-increment", |198|:"stop-color", fontFamily:"-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif", |298|:"-webkit-marquee-repetition", |199|:"stop-opacity", animationPlayState:"running", webkitHyphenateLimitBefore:"auto", |299|:"-webkit-marquee-style", webkitBoxOrient:"horizontal", order:"0", webkitMaskSourceType:"alpha", fontVariantNumeric:"normal", webkitColumnBreakAfter:"auto", webkitColumnBreakInside:"auto", shapeMargin:"0px", webkitMaskImage:"none", top:"auto", backgroundBlendMode:"normal", listStylePosition:"outside", bottom:"auto", gridTemplateColumns:"none", strokeWidth:"0.8695652484893799px", borderTopRightRadius:"0px", direction:"ltr", webkitBackfaceVisibility:"visible", backgroundPositionY:"0%", rx:"0px", webkitHyphenateLimitLines:"no-limit", ry:"0px", lineBreak:"auto", bufferedRendering:"auto", quotes:"", gridRow:"auto / auto", webkitBorderFit:"border", borderBottomRightRadius:"0px", alignmentBaseline:"auto", textIndent:"0px", paddingBottom:"0px", columnRuleColor:"rgb(51, 51, 51)", fontVariantCaps:"normal", backgroundSize:"auto", |60|:"column-count", columnCount:"auto", |61|:"column-fill", webkitBoxLines:"single", webkitBoxFlexGroup:"1", backgroundRepeatX:"", |62|:"column-gap", glyphOrientationHorizontal:"0deg", lineHeight:"20px", |63|:"column-rule-color", borderBlockEndWidth:"0px", opacity:"1", webkitBackgroundClip:"border-box", webkitBoxPack:"start", |64|:"column-rule-style", |65|:"column-rule-width", borderImageWidth:"1", fontVariantLigatures:"normal", |20|:"background-repeat", |66|:"column-span", |21|:"background-size", |67|:"column-width", gridTemplateAreas:"none", tableLayout:"auto", |22|:"baseline-shift", |68|:"content", webkitBorderImage:"none", |23|:"border-bottom-color", |69|:"counter-increment", animationName:"none", letterSpacing:"normal", |24|:"border-bottom-left-radius", |25|:"border-bottom-right-radius", backgroundClip:"border-box", transitionDelay:"0s", |26|:"border-bottom-style", |27|:"border-bottom-width", webkitCursorVisibility:"auto", |28|:"border-collapse", webkitTextDecorationsInEffect:"none", |29|:"border-image-outset", fill:"rgb(0, 0, 0)", webkitHyphenateCharacter:"auto", animation:"", pageBreakInside:"auto", lightingColor:"rgb(255, 255, 255)", webkitLineClamp:"none", perspectiveOriginY:"", textDecorationThickness:"auto", marginInlineStart:"0px", borderTopStyle:"none", placeContent:"normal normal", backgroundRepeatY:"", textUnderlinePosition:"auto", wordBreak:"normal", columnRuleStyle:"none", webkitUserModify:"read-only", columnSpan:"none", strokeOpacity:"1", webkitMarqueeRepetition:"infinite", webkitMarginCollapse:"", flexBasis:"auto", colorInterpolationFilters:"linearRGB", gap:"normal normal", webkitMask:"", gridArea:"auto / auto / auto / auto", gridTemplate:"none / none / none", resize:"none", borderColor:"rgb(128, 128, 128)", zIndex:"auto", webkitTextCombine:"none", webkitTextStrokeWidth:"0px", borderBottomStyle:"none", fontVariant:"normal", scrollSnapType:"none", textDecoration:"none", webkitTextEmphasisColor:"rgb(51, 51, 51)", orphans:"auto", scrollPaddingRight:"0px", borderImageOutset:"0px", webkitTextDecoration:"none solid rgb(51, 51, 51)", |110|:"grid-auto-rows", marginLeft:"0px", transitionTimingFunction:"ease", |210|:"table-layout", stroke:"none", stopOpacity:"1", |111|:"grid-column-end", |310|:"-webkit-mask-position", stopColor:"rgb(0, 0, 0)", webkitPrintColorAdjust:"economy", |211|:"text-align", unicodeBidi:"normal", |112|:"grid-column-start", |311|:"-webkit-mask-repeat", page:"", |212|:"text-anchor", |113|:"grid-row-end", |312|:"-webkit-mask-size", fontFeatureSettings:"normal", |213|:"text-decoration", |114|:"grid-row-start", webkitMarqueeIncrement:"6px", webkitUserSelect:"text", |313|:"-webkit-mask-source-type", gridAutoRows:"auto", |214|:"text-decoration-color", |115|:"grid-template-areas", |140|:"marker-start", |314|:"-webkit-nbsp-mode", scrollPaddingBottom:"0px", |215|:"text-decoration-line", |240|:"width", |116|:"grid-template-columns", |141|:"mask", |315|:"-webkit-print-color-adjust", borderBlockStartColor:"rgb(128, 128, 128)", |340|:"fullscreen-inset-top", |216|:"text-decoration-skip", fontVariantEastAsian:"normal", |241|:"will-change", |117|:"grid-template-rows", fontVariationSettings:"normal", |142|:"mask-type", |316|:"-webkit-rtl-ordering", marginBlockEnd:"20px", |341|:"safe-area-inset-top", |217|:"text-decoration-style", minWidth:"0px", |242|:"word-break", |118|:"hanging-punctuation", zoom:"1", |143|:"max-height", |317|:"-webkit-text-combine", |218|:"text-indent", gridAutoColumns:"auto", |243|:"word-spacing", |119|:"height", verticalAlign:"baseline", |144|:"max-width", |318|:"-webkit-text-decorations-in-effect", imageRendering:"auto", maxBlockSize:"none", |219|:"text-overflow", outlineWidth:"0px", |244|:"word-wrap", shapeRendering:"auto", webkitLineGrid:"none", |145|:"min-height", |319|:"-webkit-text-emphasis-color", |170|:"place-content", enableBackground:"", |245|:"writing-mode", |270|:"-webkit-box-pack", |146|:"min-width", animationDelay:"0s", |171|:"place-items", boxShadow:"none", scrollSnapMarginLeft:"0px", webkitBorderRadius:"", |246|:"x", |271|:"-webkit-box-reflect", |147|:"mix-blend-mode", |172|:"place-self", |247|:"y", blockSize:"618.8043823242188px", |272|:"-webkit-box-shadow", |148|:"object-fit", columnRuleWidth:"0px", |173|:"pointer-events", backgroundImage:"none", |248|:"z-index", |273|:"-webkit-clip-path", |149|:"object-position", transitionDuration:"0s", |174|:"position", |249|:"zoom", |274|:"-webkit-column-axis", webkitColumnAxis:"auto", |175|:"r", borderImageSlice:"100%", |275|:"-webkit-column-break-after", borderCollapse:"collapse", |176|:"resize", |276|:"-webkit-column-break-before", webkitLineBoxContain:"block inline replaced", |177|:"right", |277|:"-webkit-column-break-inside", |178|:"row-gap", borderLeft:"0px none rgb(128, 128, 128)", |font|:"normal normal normal normal 14.000000953674316px/20px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif", webkitMarginAfterCollapse:"collapse", |278|:"-webkit-column-progression", |outline|:"rgb(51, 51, 51) none 0px", scrollPaddingLeft:"0px", |90|:"flood-opacity", |179|:"rx", markerStart:"none", strokeDasharray:"none", |91|:"font-family", |279|:"-webkit-cursor-visibility", flexShrink:"1", webkitMaskRepeatX:"", |92|:"font-optical-sizing", transformBox:"border-box", |93|:"font-size", |94|:"font-stretch"}

on parseJSONAsRecord(jsRes)
  set jsonString to current application’s NSString’s stringWithString:jsRes
  
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)
  
return aJsonDict as record
end parseJSONAsRecord

★Click Here to Open This Script 

(Visited 176 times, 2 visits today)
Posted in Internet JavaScript JSON | Tagged 10.12savvy 10.13savvy 10.14savvy NSJSONSerialization NSString Safari | 1 Comment

Post navigation

  • Older posts

電子書籍(PDF)をオンラインストアで販売中!

Google Search

Popular posts

  • AppleScriptによるWebブラウザ自動操縦ガイド
  • macOS 13, Ventura(継続更新)
  • ドラッグ&ドロップ機能の未来?
  • macOS 12.x上のAppleScriptのトラブルまとめ
  • PFiddlesoft UI Browserが製品終了に
  • macOS 12.3 beta 5、ASの障害が解消される(?)
  • SF Symbolsを名称で指定してPNG画像化
  • 新刊発売:AppleScriptによるWebブラウザ自動操縦ガイド
  • macOS 12.3 beta4、まだ直らないASまわりの障害
  • macOS 12.3上でFinder上で選択中のファイルをそのままオープンできない件
  • Safariで表示中のYouTubeムービーのサムネイル画像を取得
  • macOS 12のスクリプトエディタで、Context Menu機能にバグ
  • Pixelmator Pro v2.4.1で新機能追加+AppleScriptコマンド追加
  • 人類史上初、魔導書の観点から書かれたAppleScript入門書「7つの宝珠」シリーズ開始?!
  • CotEditor v4.1.2でAppleScript系の機能を追加
  • macOS 12.5(21G72)がリリースされた!
  • UI Browserがgithub上でソース公開され、オープンソースに
  • Pages v12に謎のバグ。書類上に11枚しか画像を配置できない→解決
  • 新発売:AppleScriptからSiriを呼び出そう!
  • iWork 12.2がリリースされた

Tags

10.11savvy (1102) 10.12savvy (1243) 10.13savvy (1391) 10.14savvy (586) 10.15savvy (434) 11.0savvy (274) 12.0savvy (174) 13.0savvy (34) CotEditor (60) Finder (47) iTunes (19) Keynote (97) NSAlert (60) NSArray (51) NSBezierPath (18) NSBitmapImageRep (21) NSBundle (20) NSButton (34) NSColor (51) NSDictionary (27) NSFileManager (23) NSFont (18) NSImage (42) NSJSONSerialization (21) NSMutableArray (62) NSMutableDictionary (21) NSPredicate (36) NSRunningApplication (56) NSScreen (30) NSScrollView (22) NSString (118) NSURL (97) NSURLRequest (23) NSUTF8StringEncoding (30) NSUUID (18) NSView (33) NSWorkspace (20) Numbers (55) Pages (36) Safari (41) Script Editor (20) WKUserContentController (21) WKUserScript (20) WKWebView (23) WKWebViewConfiguration (22)

カテゴリー

  • 2D Bin Packing
  • 3D
  • AirDrop
  • AirPlay
  • Animation
  • AppleScript Application on Xcode
  • beta
  • Bluetooth
  • Books
  • boolean
  • bounds
  • Bug
  • Calendar
  • call by reference
  • Clipboard
  • Code Sign
  • Color
  • Custom Class
  • dialog
  • drive
  • exif
  • file
  • File path
  • filter
  • folder
  • Font
  • Font
  • GAME
  • geolocation
  • GUI
  • GUI Scripting
  • Hex
  • History
  • How To
  • iCloud
  • Icon
  • Image
  • Input Method
  • Internet
  • iOS App
  • JavaScript
  • JSON
  • JXA
  • Keychain
  • Keychain
  • Language
  • Library
  • list
  • Locale
  • Machine Learning
  • Map
  • Markdown
  • Menu
  • Metadata
  • MIDI
  • MIME
  • Natural Language Processing
  • Network
  • news
  • Noification
  • Notarization
  • Number
  • Object control
  • OCR
  • OSA
  • PDF
  • Peripheral
  • PRODUCTS
  • QR Code
  • Raw AppleEvent Code
  • Record
  • recursive call
  • regexp
  • Release
  • Remote Control
  • Require Control-Command-R to run
  • REST API
  • Review
  • RTF
  • Sandbox
  • Screen Saver
  • Script Libraries
  • sdef
  • search
  • Security
  • selection
  • shell script
  • Shortcuts Workflow
  • Sort
  • Sound
  • Spellchecker
  • Spotlight
  • SVG
  • System
  • Tag
  • Telephony
  • Text
  • Text to Speech
  • timezone
  • Tools
  • Update
  • URL
  • UTI
  • Web Contents Control
  • WiFi
  • XML
  • XML-RPC
  • イベント(Event)
  • 未分類

アーカイブ

  • 2023年3月
  • 2023年2月
  • 2023年1月
  • 2022年12月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年10月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年12月
  • 2019年11月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年10月
  • 2018年9月
  • 2018年8月
  • 2018年7月
  • 2018年6月
  • 2018年5月
  • 2018年4月
  • 2018年3月
  • 2018年2月

https://piyomarusoft.booth.pm/items/301502

メタ情報

  • 登録
  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org

Forum Posts

  • 人気のトピック
  • 返信がないトピック

メタ情報

  • 登録
  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org
Proudly powered by WordPress
Theme: Flint by Star Verte LLC