- JXA-ObjC
- $
- 引入框架
- 转换 ObjC 的语法
- 判断是否是文件夹
- demo
- 新建终端窗口
- 在 finder 右键状态栏
- 通过 apple 脚本的形式解析并运行 js 文件
- 如何转换报错信息
- doShell
- 将文件复制到剪贴板
- 打开文件选择器
- 文件选择框 var require = function (path) {
- 获得指定目录下的所有文件列表
- 获取指定文件夹下的文件信息
- 网络请求
- 准备
- 在 safari 中调试脚本
- 在终端中运行 JavaScript
- 词典
- System Events
- keystrokes
- 剪贴板
- 网络连接
- 菜单栏
- 操作 UI 元素
- 对象结构
- 日历
- 元素结构
- code
- System Events
- example
- 简单的示意
- 全局属性
- 访问 Application
- 获得应用的信息
- 获得和设置属性(Properties)
- 元素索引
- 过滤器
- 执行命令
- 创建对象
- 弹窗
- 谷歌浏览器打开文档
- 对谷歌浏览器的第一个窗口的第一个标签页执行 js
- 日历
- 邮件
- keynote
- 文本编辑
- 交互提示框
- 文件选择框
- 本地文件 IO
- 语音反馈
- 执行 shell 命令
- cocoa
- 移动鼠标到屏幕中央
- 鼠标点击特定坐标点
- 使用 C 创建应用
- 参考文献
JXA-ObjC
$
$ 是所有函数调用的主要访问点
js">str = $ 。NSString 。ALLOC 。initWithUTF8String ('foo' )
str 。writeToFileAtomically ('/ tmp / foo' , true )
引入框架
默认情况下直接使用$.NSBeep(),系统会报错,原因是 NSBeep 是 ObjC 的一个框架下的方法,调用时需要引入
js">
转换 ObjC 的语法
ObjeCinit(contentRect:styleMask:backing:defer:)
=>JXAinitContentRectStyleMaskBackingDefer()
在 ObjectiveC 定义中,每个冒号前面的每个“名称”代表函数期望的特定变量类型。我们可以在声明中看到以下每种类型:
init(contentRect: NSRect,
styleMask style: NSWindow.StyleMask,
backing backingStoreType: NSWindow.BackingStoreType,
defer flag: Bool)
赋予函数’contentRect’的第一个参数应为type NSRect
赋予函数’styleMask’的第二个参数应为type NSWindow.StyleMask
赋予函数“ backing”的第三个参数应为type NSWindow.BackingStoreType
给我们的函数’defer’的第四个参数应该是类型Boolean
因此,当我们调用函数时,我们需要按照声明中定义的顺序提供这些参数。
在目标 C 中,通常以以下方式执行上述功能:
js">NSRect frame = NSMakeRect(0, 0, 200, 200);
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]
判断是否是文件夹
BOOL isDir;
NSFileManager *fileManager = [[NSFileManager alloc] init];
if ([fileManager fileExistsAtPath:fontPath isDirectory:&isDir] ...
转换为 jxa 语法
js">isDir = {}
$.NSFileManager.alloc.init.fileExistsAtPathIsDirectory(
'/Users/luomingyang/Desktop/1元抢199-100大额券.png',
isDir
)
console.log(Object.keys(isDir)) //==> []
js">isDir = Ref() //set up a variable which can be passed by reference to ObjC functions.
$.NSFileManager.alloc.init.fileExistsAtPathIsDirectory(
'/Users/luomingyang/Desktop/1元抢199-100大额券.png',
isDir
)
isDir[0] //get the data from the variable
demo
新建终端窗口
js">var app = Application.currentApplication()
var Terminal = Application('Terminal')
app.includeStandardAdditions = true
termWindow = Terminal.windows[0]
termTabs = termWindow.tabs
newTab = new Terminal.Tab()
try {
termTabs.push(newTab)
} catch (e) {
app.say(stringifyObj(e))
}
在 finder 右键状态栏
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var se = Application('System Events')
se.processes['Finder'].windows[0].toolbars[0].actions['AXShowMenu'].perform()
================================
js__170">通过 apple 脚本的形式解析并运行 js 文件
js">
var osascript = require('osascript')
var fs = require('fs');
fs.createReadStream('/Users/luomingyang/Luo_MingYang/JS&JSA/note_JSA.js')
.pipe(osascript())
.pipe(process.stdout);
=========================
var osascript = require('osascript').eval;
Run JavaScript text through OSA
osascript('console.log("Hello, world!");').pipe(process.stdout);
=========================
var osascript = require('osascript').file
Run JavaScript file through OSA
osascript(
'/Users/luomingyang/Library/Mobile Documents/iCloud~com~coderforart~iOS~MWeb/Documents/MD_All/draft.js',
function (err, data) {
console.log(err, data)
}
)
如何转换报错信息
js">function stringifyObj(specifier) {
return Automation.getDisplayString(specifier)
}
doShell
js">var app = Application.currentApplication()
var host = 'digitalocean.com'
app.doShellScript('ping -cl' + host)
var Calendar = Application('Calendar')
var projectCalendars = Calendar.calendars.whose({ name: '日历' }) //在日历中查找名称为'日历'的日历表
var projectCalendar = projectCalendars[0] //项目日历为第一个结果
var events = projectCalendar.events.whose({ summary: 'JXA' }) //选中描述日程中
var event = events[0] //第一个日程
var attendee = Calendar.Attendee({ email: 'example@apple.com' }) //添加参会者
event.attendees.push(attendee)
Calendar.reloadCalendars()
将文件复制到剪贴板
使用该命令后,系统会有卡顿
js">ObjC.import('AppKit')
/** @param {string} path */
function copyPathToClipboard(path) {
const pasteboard = $.NSPasteboard.generalPasteboard
pasteboard.clearContents
return pasteboard.setPropertyListForType($([path]), $.NSFilenamesPboardType)
}
copyPathToClipboard('/Users/luomingyang/Desktop/1元抢199-100大额券.png')
打开文件选择器
文件选择框 var require = function (path) {
js">app = Application.currentApplication()
app.includeStandardAdditions = true
js">app.chooseFile()
app.chooseFile({ withPrompt: 'Please select the first image' })
// => Path("/Users/dtinth/npm-debug.log")
// OR !! Error on line 1: Error: User canceled.
// 文件类型过滤
app.chooseFile({
withPrompt: 'Select the first image',
ofType: ['public.jpeg', 'public.png'],
})
获得指定目录下的所有文件列表
最简单的方法
js">var strPath = '/Users/luomingyang/Alfred_Workflow-1.40.0.dist-info'
var appSys = Application('System Events'),
lstHarvest = appSys.folders.byName(strPath).diskItems.name()
console.log(lstHarvest)
速度提升 40%的方法
js">var strPath = '/Users/luomingyang/Alfred_Workflow-1.40.0.dist-info'
var fm = $.NSFileManager.defaultManager,
oURL = $.NSURL.fileURLWithPathIsDirectory(strPath, true),
lstFiles = ObjC.unwrap(
fm.contentsOfDirectoryAtURLIncludingPropertiesForKeysOptionsError(
oURL,
[],
1 << 2,
null
)
),
lstHarvest = []
lstFiles.forEach(function (oItem) {
lstHarvest.push(ObjC.unwrap(oItem.path))
})
console.log(lstHarvest)
速度提升 300%的方法
js">;(function () {
// listDirectory :: FilePath -> [FilePath]
function listDirectory(strPath) {
fm = fm || $.NSFileManager.defaultManager
return ObjC.unwrap(
fm.contentsOfDirectoryAtPathError(
$(strPath).stringByExpandingTildeInPath,
null
)
).map(ObjC.unwrap)
}
var fm = $.NSFileManager.defaultManager
return listDirectory('~/Desktop')
})()
获取指定文件夹下的文件信息
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var finderApp = Application('Finder')
var itemList = finderApp.selection()
var oItem = itemList[0]
var oItemPaths = getPathInfo(oItem)
/* --- oItemPaths Object Keys ---
oItemPaths.itemClass
oItemPaths.fullPath
oItemPaths.parentPath
oItemPaths.itemName
*/
console.log(JSON.stringify(oItemPaths, undefined, 4))
//--- EXAMPLE RESULTS ---
/* {
"itemClass": "documentFile",
"fullPath": "/Users/luomingyang/Luo_MingYang/JS&JSA/note_JSA.md",
"parentPath": "/Users/luomingyang/Luo_MingYang/JS&JSA",
"itemName": "note_JSA.md"
} */
//~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function getPathInfo(pFinderItem) {
// @Path @Finder @File @Folder
/* Ver 2.0 2017-08-01
---------------------------------------------------------------------------------
PURPOSE: Get Object/Record of Finder Item Path Info
PARAMETERS:
• pFinderItem | object | Finder Item object
RETURNS: Object with these Keys:
itemClass
fullPath
parentPath
itemName
AUTHOR: JMichaelTX
—————————————————————————————————————————————————————————————————————————————————
*/
var itemClass = pFinderItem.class() // returns "folder" if item is a folder.
var itemURL = pFinderItem.url()
var fullPath = decodeURIComponent(itemURL).slice(7)
//--- Remove Trailing "/", if any, to handle folder item ---
var pathElem = fullPath.replace(/\/$/, '').split('/')
var itemName = pathElem.pop()
var parentPath = pathElem.join('/')
return {
itemClass: itemClass,
fullPath: fullPath,
parentPath: parentPath,
itemName: itemName,
}
}
网络请求
js">$.NSURLSession.sharedSession.dataTaskWithURLCompletionHandler(
$.NSURL.URLWithString('https://www.baidu.com'),
(data, resp) => {
console.log(resp.statusCode)
console.log(
ObjC.unwrap(
$.NSString.alloc.initWithDataEncoding(data, $.NSASCIIStringEncoding)
).split('\n')[0]
)
}
).resume
js">var urlStr = 'https://stackoverflow.com/'
var htmlStr = getHTMLSource(urlStr)
htmlStr.substring(0, 200)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function getHTMLSource(pURL) {
// @HTML @Web @URL @OBjC
/* Ver 1.0 2017-06-24
---------------------------------------------------------------------------------
PURPOSE: Get the HTML Source Code for the specified URL.
PARAMETERS:
• pURL | string | URL of Page to get HTML of
RETURNS: | string | html source of web page
REF:
1. AppleScript Handler by @ccstone
on getUrlSource:urlStr
—————————————————————————————————————————————————————————————————————————————————
*/
// framework "Foundation" is built-in to JXA, and is referenced by the "$"
var nsURL = $.NSURL.URLWithString(pURL)
var nsHTML = $.NSData.dataWithContentsOfURL(nsURL)
var nsHTMLStr = $.NSString.alloc.initWithDataEncoding(
nsHTML,
$.NSUTF8StringEncoding
)
var htmlStr = ObjC.unwrap(nsHTMLStr)
return htmlStr
}
准备
在 safari 中调试脚本
-
打开 safari 的
开发
、macbook
和自动显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vcex6PUn-1612578945245)(https://files.catbox.moe/mnbhtd.png)] -
打开脚本编辑器,输入以下代码,运行之后,safari 会出现 debug 窗口
js">var x = false
debugger
if (x) {
console.log("Why isn't this being called?")
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7y8R0FO-1612578945256)(https://files.catbox.moe/x8e9ek.png)]
在终端中运行 JavaScript
输入命令,进入交互编辑界面
osascript -i -l JavaScript
执行 js 文件
/usr/bin/env osascript -l JavaScript "/Users/path/demo.js"
词典
System Events
keystrokes
Complete list of AppleScript key codes
js">var se = Application('System Events')
se.keyCode(36) // Press Enter
se.keystroke('hello') //模拟输入子母,仅支持英文,中文乱码
se.keystroke('a', { using: 'command down' }) // Press Cmd-A
se.keystroke(' ', { using: ['option down', 'command down'] }) // Press Opt-Cmd-Space
剪贴板
js">//--- GET A REF TO CURRENT APP WITH STD ADDITONS ---
var app = Application.currentApplication()
app.includeStandardAdditions = true
//--- Set the Clipboard so we can test for no selection ---
app.setTheClipboardTo('[NONE]')
//--- Get the Text on the Clipboard ---
var clipStr = app.theClipboard()
console.log(clipStr) //[NONE]
网络连接
se.connect(se.networkPreferences.services["WI-FI"])
菜单栏
js">>> var ts=se.processes.byName('终端')
>> ts.menuBars[0].menuBarItems.length //顶部菜单栏的数量
=> 7
>> ts.menuBars[0].menuBarItems[0].name()
=> "Apple"
>> ts.menuBars[0].menuBarItems[1].name()
=> "终端"
>> ts.menuBars[0].menuBarItems[1].menus[0].menuItems[0].click()
=> Application("System Events").applicationProcesses.byName("Terminal").menuBars.at(0).menuBarItems.byName("终端").menus.byName("终端").menuItems.byName("关于终端")
js">var fileMenu=ts.menuBars[0].menuBarItems[1]
var items = fileMenu.menus[0].menuItems(); // Note ()
for (var item, i = 0, j = items.length; i < j; i++) {
item = items[i];
if (item.enabled() &&
/^Log out myname/.test(item.title()) { // 去除分割线
item.click();
}
}
操作 UI 元素
finder
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPaxdFRJ-1612578945258)(https://files.catbox.moe/b4lt2z.png)]
se.processes['Finder'].windows[0].toolbars[0].actions['AXShowMenu'].perform()
dock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vu4PFzlS-1612578945264)(https://files.catbox.moe/70leav.png)]
js">se.processes['Dock'].lists[0].uiElements[0].actions['AXShowMenu'].perform()
// 无效的索引
se.processes['Dock'].lists[0].uiElements[0].menus[0].menuItems()
// Click 'Hide'
se.processes['Dock'].lists[0].uiElements[0].menus[0].menuItems['Hide'].click()
使用ui Brower
查看 ui 结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5uMHr3aa-1612578945265)(https://files.catbox.moe/b47ud5.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DbOU7GRX-1612578945279)(https://90-cucc-dd.tv002.com/down/06b059c90d8adb8cd456801bceb1ce48/UI%20Browser%203.0.2_www.imacapp.cn.dmg?cts=wt-f-D101A206A254A90F6f8d6&ctp=101A206A254A90&ctt=1612261806&limit=1&spd=550000&ctk=06b059c90d8adb8cd456801bceb1ce48&chk=3a187f04c101eb2d6b935e2c1693a6a6-4151190&mtd=1)]
js">se.processes['WeChat'].windows[0].splitterGroups[0].splitterGroups[0].scrollAreas[0].tables[0].rows[33].uiElements.uiElements[0].staticTexts[0].value()
=>["你的身份证号码是多少"]
// 获取并设置输入框的值
se.processes['WeChat'].windows[0].splitterGroups[0].splitterGroups[0].scrollAreas[1].textAreas[0].value='input'
// 获取用户名
>> se.processes['WeChat'].windows[0].splitterGroups[0].scrollAreas[0].tables[0].rows[10].uiElements[0].staticTexts[0].value()
=> "小七"
js">Application('Safari').activate()
// Access the Safari process of System Events
var SystemEvents = Application('System Events')
var Safari = SystemEvents.processes['Safari']
// Call the click command, sending an array of coordinates [x, y]
Safari.click({ at: [46, 50] })
对象结构
- Application
- properties
- appearancePreferences
- applicationSupportFolder[应用资源文件夹路径]
- applicationsFolder
- currentDesktop
- currentScreenSaver
- [屏幕保护程序:用法 se.start(se.currentScreenSaver)]
- currentUser
- Application(“System Events”).users.byName(“luomingyang”)
- desktopFolder
- desktopPicturesFolder
- folderActionScriptsFolder
- [脚本存放文件夹:’/Users/luomingyang/Library/Scripts/Folder Action Scripts’]
- fontsFolder
- frontmost[是否位于最前面]
- networkPreferences
- se.connect(se.networkPreferences.services[“WI-FI”])
- elements
- alias
- applicationProces
- commands
- properties
luo-mingyang:JS&JSA luomingyang$ osascript -i -l JavaScript
>> let mail=Application('Mail')
=> undefined
>> mail.name()
=> "邮件"
>> mail.version()
=> "14.0"
>> mail.frontmost()
=> false
>> mail.windows.length
=> 1
>> mail.windows[0].name()
=> "垃圾邮件 — QQ"
>> mail.windows[0].id()
=> 11098
>> mail.windows[0].index()
=> 1
>> mail.windows[0].bounds()
=> {"x":172, "y":26, "width":1011, "height":1025}
>> mail.windows[0].closeable()
=> true
>> mail.windows[0].miniaturized()
=> false
>> mail.windows[0].visible()
=> true
日历
元素结构
- Application
- calendars
- properties
- color(get/set)
- description(get/set)
- name(get/set)
- element
- event
- properties
- summary
- 主标题
- description
- endDate
- startDate
- alldayEvent
- status
- url
- location
- summary
- elements
- attendee
- email(get)
- displayName(get)
- participationStatus(get)[是否接受邀约]
- display alarm
- mail alarm
- open file alarm
- sound alarm
- attendee
- properties
- commands
- show()
- event
- properties
- documents
- windows
- calendars
code
>> let cal=Application('日历')
=> undefined
>> cal.name()
=> "日历"
>> cal.calendars.length
=> 15
>> cal.calendars[0].name()
=> "Demo"
>> cal.calendars[0].color()
=> [0.9893491864204407, 0.7216296792030334, 0.05394064262509346]
>> cal.calendars[0].writable()
=> true
>> cal.calendars[0].description()
=> "新日历"
# 给第一个日历的第一个对象添加邮箱
cal.calendars[0].events[0].attendees.push(cal.Attendee({email:'11@163.com'}))
# 创建日历
cal.Calendar({name:'日历'}).make()
# result:Application("日历").calendars.byId("FA9B832C-E85F-4E67-B6AC-87D352DDF110")
# 定位日历
cal.calendars.whose({name:'Demo'}).name()
=> ["Demo"]
# 对比dom元素
# document.getElementsByName
# set 日历的属性
>> cal.calendars[0].color=[0.5,0.5,0.5]
=> [0.5, 0.5, 0.5]
# 删除日历
cal.delete(cal.calendars[1])
example
简单的示意
js">Mail = Application('Mail')
Mail.id()
Mail.running()
全局属性
JavaScript for Automation 是 JavaScript 的宿主环境,它添加了以下的全局属性
- Automation
- Application
- Library
- Path
- Progress
- ObjectSpecifier
- delay
- console.log
- ObjC
- Red
- $
访问 Application
- 通过名称
Application('Mail')
- 通过 id
Application('com.apple.mail')
- 当前应用
Application.currentApplication()
获得应用的信息
- 访问属性
Mail.name
- 访问元素
Mail.outgoingMessages[0]
- 执行方法
Mail.open()
- 新建对象
Mail.OutgoingMessage(...)
- 打印对象的属性信息
js">var kme = Application('Keyboard Maestro Engine')
var kmeProps = kme.properties()
console.log(JSON.stringify(kmeProps, null, '\t'))
// {
// "frontmost": false,
// "pcls": "application",
// "name": "Keyboard Maestro Engine",
// "version": "9.2"
// }
- 查看对象的类
//"application"```
### 获得和设置属性(Properties)
`subject = Mail.inbox.messages[0].subject()`
`Mail.outgoingMessages[0].subject = 'Hello world'`
### 元素索引
index
```js
window = Mail.windows.at(0)
window = Mail.windows[0]
name
js">window = Mail.windows.byName('New Message')
window = Mail.windows['New Message']
id
window = Mail.windows.byId(412)
过滤器
过滤名称
js">{ name: 'JavaScript for Automation' }
>> se.processes.byName('Adobe Photoshop 2021').menuBars[0].menuBarItems[2].menus[0].menuItems.whose({name:'打开...'}).length
=> 1
内容过滤
js">whose({name:{_contains:'打开'}}).name()
>> se.processes.byName('Adobe Photoshop 2021').menuBars[0].menuBarItems[2].menus[0].menuItems.whose({name:{_contains:'打开'}}).name()
=> ["打开...", "打开为智能对象...", "最近打开文件"]
开头结尾过滤
{ name: {_beginsWith: 'JavaScript' } }
示意
js">Mail = Application('Mail')
Mail.inbox.messages.whose({
\_or: [
{ subject: { _contains: "JavaScript" } },
{ subject: { _contains: "Automation" } }
]
})"
执行命令
message.open()
js">Safari.doJavaScript('alert("Hello world")', {
in: Safari.windows[0].tabs[0],
})
创建对象
message = Mail.OutgoingMessage().make()
新建邮件,并输入描述
js">message = Mail.OutgoingMessage({
subject: 'Hello world',
visible: true,
})
Mail.outgoingMessages.push(message)
para = TextEdit.Paragraph({}, 'Some text')
TextEdit.documents[0].paragraphs.push(para)
message = Mail.OutgoingMessage().make()
message.subject = 'Hello world'
弹窗
js">app = Application.currentApplication()
app.includeStandardAdditions = true
app.say('Hello world')
app.displayDialog('Please enter your email address', {
withTitle: 'Email',
defaultAnswer: 'your_email@site.com',
})
谷歌浏览器打开文档
js">// grab the chrome object
var chrome = Application('Google Chrome')
// create a new chrome window
var window = chrome.Window().make()
// set the links you want to open
var links = [
'https://gmail.com',
'https://soundcloud.com',
'https://twitter.com',
'https://aws.amazon.com',
]
// loop through the links
for (i = 0; i < links.length; i++) {
// set the url for the tab
window.tabs[i].url = links[i]
// check to see if we have a next tab
if (links[i + 1] !== undefined) {
// create a new tab and push it to the window
window.tabs.push(new chrome.Tab())
}
}
js_912">对谷歌浏览器的第一个窗口的第一个标签页执行 js
chrome.windows[0].tabs[0].execute({javascript:'alert(1)'})
js">// grab the chrome object
var chrome = Application('Google Chrome')
// grab all of the chrome windows
var windows = chrome.windows
// loop through the chrome windows
for (i = 0; i < windows.length; i++) {
// grab all of the tabs for the window
var tabs = windows[i].tabs
// loop through the tabs
for (j = 0; j < tabs.length; j++) {
// grab the url for the tab
var url = tabs[j].url()
// check to see if the tab url matches "soundcloud.com"
if (url.match(/soundcloud.com/)) {
// in the tab lets execute some javascript
tabs[j].execute({
// select the .playControl div and click it!
javascript: "document.querySelector('.playControl').click();",
})
}
}
}
自动点击 youtube 网页的按钮
js">var chrome = Application("Google Chrome");
var windows = chrome.windows;
for(i = 0; i < windows.length; i++){
var tabs = windows[i].tabs;
for(j = 0; j < tabs.length; j++){
var url = tabs[j].url();
if(url.match(/youtube.com/)){
tabs[j].execute({ javascript: "document.querySelector('.ytp-play-button').click();" });
}
}
日历
寻找日程并添加受邀人的名称
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10mFJ5ew-1612578945286)(https://files.catbox.moe/j5757e.png)]
js">var app = Application.currentApplication()
var Calendar = Application('Calendar')
var projectCalendars = Calendar.calendars.whose({ name: '日历' }) //在日历中查找名称为'日历'的日历表
var projectCalendar = projectCalendars[0] //项目日历为第一个结果
var events = projectCalendar.events.whose({ summary: 'JXA' }) //选中描述日程中
var event = events[0] //第一个日程
var attendee = Calendar.Attendee({ email: 'example@apple.com' }) //添加参会者
event.attendees.push(attendee)
Calendar.reloadCalendars()
在 mac 上新建日历
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2nqTa3x-1612578945288)(https://files.catbox.moe/i00awm.png)]
js">var Calendar = Application('Calendar')
var calendarName = '日历'
var calendarDescription = '新日历'
var newCalendar = Calendar.Calendar({
name: calendarName,
description: calendarDescription,
}).make()
在今天新建日程
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var Calendar = Application('Calendar')
var eventStart = app.currentDate()
eventStart = eventStart
eventStart.setDate(eventStart.getDate())
eventStart.setHours(15)
eventStart.setMinutes(0)
eventStart.setSeconds(0)
var eventEnd = new Date(eventStart.getTime())
eventEnd.setHours(16)
var projectCalendars = Calendar.calendars.whose({ name: '日历' })
//Use the first result that matches name search
var projectCalendar = projectCalendars[0]
var event = Calendar.Event({
summary: 'Important Meeting!',
startDate: eventStart,
endDate: eventEnd,
})
projectCalendar.events.push(event)
添加 1 小时后的日程
js">// Sometimes on first run it fails with invalid index, but unable to repeat
var app = Application.currentApplication()
app.includeStandardAdditions = true
Requires "Demo" calendar with events
//Create new event starting now and ending in one hour
//Set Calendar to be the Application Calendar
var Calendar = Application('Calendar')
//Set name of Calendar you are using
var calendarUsed = '日历'
//use Demo as the used Calendar by searching for it.
var projectCalendars = Calendar.calendars.whose({ name: calendarUsed })
//Use the first result that matches name search
var projectCalendar = projectCalendars[0]
//You need the time now as an object
var timeNow = new Date()
//inAnHour as well
var inAnHour = new Date()
//Create Date object one hour ago for search criteria
inAnHour.setHours(timeNow.getHours() + 1)
//Create new event with values from pervious event and end time of timeNow
newEvent = Calendar.Event({
summary: 'Demo Event',
startDate: timeNow,
endDate: inAnHour,
})
//Fails with invalid index on first run.
projectCalendar.events.push(newEvent)
删除日历中特定项
js">Requires "Demo" calendar with events
//Creates new Event with same startDate as current event and endDate as Now
// Deletes the event it replaces
// Add standard library for dialog boxes
var app = Application.currentApplication()
app.includeStandardAdditions = true
//Set Calendar to be the Application Calendar
var Calendar = Application('Calendar')
//Set name of Calendar you are using
var calendarUsed = 'Demo'
//use Demo as the used Calendar by searching for it.
var projectCalendars = Calendar.calendars.whose({ name: calendarUsed })
//Use the first result that matches name search
var projectCalendar = projectCalendars[0]
var testEvents = projectCalendar.events.whose({ summary: 'Demo Event' })
//set recentEvent to be the first in the results of search.
//TODO deal with more than one search result
var testEvent = testEvents[0]
eventId = testEvent.id()
//Delete the event with the old end time.
Calendar.delete(projectCalendar.events.byId(eventId))
展示特定日程
js">var Calendar = Application('Calendar')
var projectCalendars = Calendar.calendars.whose({ name: '日历' })
var projectCalendar = projectCalendars[0]
var events = projectCalendar.events.whose({ summary: 'Important Meeting!' })
var event = events[0]
event.show()
查看最近的日程
js">//Event elements can be added, properties cannot be changed
Requires "Demo" calendar with events
//Set Calendar to be the Application Calendar
var Calendar = Application('Calendar')
//use Demo as the used Calendar by searching for it.
var projectCalendars = Calendar.calendars.whose({ name: 'Demo' })
//Use the first result that matches name search
var projectCalendar = projectCalendars[0]
//new keyword makes Date object
var timeNow = new Date()
//Creation of a new event with same start and end time as the pulled event
newEvent = Calendar.Event({
summary: 'TEST',
startDate: timeNow,
endDate: timeNow,
})
// Push the event to the calendar
projectCalendar.events.push(newEvent)
//Choose which event I will edit. In this case first one. Could also be .last or array value []
var event = projectCalendar.events.last()
// attendee is made as a function. Class Event contains element attendeess, which is why I can add(push) attendees.
var attendee = Calendar.Attendee({ email: 'example1@apple.com' })
event.attendees.push(attendee)
event.attendees[0]()
event.startDate()
//new Event(myEvent)
//projectCalendar.events.length;
日历基础信息
js">//array of object's enumerable string properties
Object.keys(Application('Calendar').calendars[0].events)
//Result : ["0", "1", "2", "3", "4"]
//Get name of calendar 12
Application('Calendar').calendars[0].name()
//Result: "Demo"
//Summary is the name of the event
Application('Calendar').calendars[0].events.summary()
//Result: ["Important Meeting!", "Important Meeting!", "TEST", "Important Meeting!", "Important Meeting!"]
邮件
js">Mail = Application('Mail')
Mail.name()
Mail.name
Mail.outgoingMessages[0]
//Mail.accounts[0].mailboxes[8].messages[0]()
//Mail.accounts[0].mailboxes[8]()
Mail.activate() //bring Mail to foreground
Mail.accounts['iCloud'].checkForNewMail()
// show latest message in inbox
var latestMsg = Mail.inbox.messages[0]
Mail.open(latestMsg)
keynote
启动 keynote 并新建特定主体和尺寸的 PPT
js">Keynote = Application('Keynote')
Keynote.launch()
//document class, make it a method, and add make verb at the end.
//newDoc = Keynote.Document().make()
//can create with properties set as record
doc = Keynote.Document({
documentTheme: Keynote.themes['基本颜色'],
width: 1024,
height: 768,
autoPlay: true,
autoLoop: true,
autoRestart: true,
maximumIdleDuration: 3,
}).make()
文本编辑
在文本编辑器的第一个窗口输入文本
js">TextEdit = Application('TextEdit')
para = TextEdit.Paragraph({}, 'Some text')
TextEdit.documents[0].paragraphs.push(para)
交互提示框
js">// Include Apple's UI elements
var app = Application.currentApplication()
app.includeStandardAdditions = true
var response = app.displayDialog("What's your name?", {
defaultAnswer: '',
withIcon: 'note',
buttons: ['Cancel', 'Continue'],
defaultButton: 'Continue',
})
// Result: {"buttonReturned":"Continue", "textReturned":"Jen"}
app.displayDialog('Hello, ' + response.textReturned + '.')
js">// Maybe from JXA cookbook
;(function () {
'use strict'
// GENERIC FUNCTIONS ------------------------------------------------------
// doesFileExist :: String -> Bool
function doesFileExist(strPath) {
var error = $()
return (
$.NSFileManager.defaultManager.attributesOfItemAtPathError(
$(strPath).stringByStandardizingPath,
error
),
error.code === undefined
)
}
// lines :: String -> [String]
function lines(s) {
return s.split(/[\r\n]/)
}
// readFile :: FilePath -> maybe String
function readFile(strPath) {
var error = $(),
str = ObjC.unwrap(
$.NSString.stringWithContentsOfFileEncodingError(
$(strPath).stringByStandardizingPath,
$.NSUTF8StringEncoding,
error
)
),
blnValid = typeof error.code !== 'string'
return {
nothing: !blnValid,
just: blnValid ? str : undefined,
error: blnValid ? '' : error.code,
}
}
// show :: a -> String
function show(x) {
return JSON.stringify(x, null, 2)
}
// TEST -------------------------------------------------------------------
var strPath = '/Users/luomingyang/Desktop/test.txt'
return doesFileExist(strPath)
? (function () {
var dctMaybe = readFile(strPath)
return dctMaybe.nothing ? dctMaybe.error : show(lines(dctMaybe.just))
})()
: 'File not found:\n\t' + strPath
})()
文件选择框
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var file = app.chooseFile({
ofType: 'txt',
withPrompt: 'Please select a text file to read:',
})
本地文件 IO
打开文件
js">app = Application.currentApplication()
app.includeStandardAdditions = true
app.openLocation('smb://xxxxxxxx')
读取 plist 文件
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var SystemEvents = Application('System Events')
var cats = SystemEvents.propertyListFiles
.byName(
'/Users/luomingyang/Luo_MingYang/Application/Alfred/Alfred.alfredpreferences/workflows/user.workflow.2AEFDEB9-2892-417C-A47C-3DEF9135C8A0/info.plist'
)
.contents.value()['uidata']
var res = JSON.stringify(cats)
console.log(res)
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
function writeTextToFile(text, file, overwriteExistingContent) {
try {
// Convert the file to a string
var fileString = file.toString()
// Open the file for writing
var openedFile = app.openForAccess(Path(fileString), {
writePermission: true,
})
// Clear the file if content should be overwritten
if (overwriteExistingContent) {
app.setEof(openedFile, { to: 0 })
}
// Write the new content to the file
app.write(text, { to: openedFile, startingAt: app.getEof(openedFile) })
// Close the file
app.closeAccess(openedFile)
// Return a boolean indicating that writing was successful
return true
} catch (error) {
try {
// Close the file
app.closeAccess(file)
} catch (error) {
// Report the error is closing failed
console.log(`Couldn't close file: ${error}`)
}
// Return a boolean indicating that writing was successful
return false
}
}
writeTextToFile('test', '/Users/luomingyang/Desktop/test.txt', false) //false 追加,true:覆盖原有内容
查看当前 osacript 路径
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
thePath = app.pathTo(this)
// Path("/usr/bin/osascript")
路径的转换
js">// Looking at the properties of diskItems
var Finder = Application('Finder')
var SystemEvents = Application('System Events')
var sourcePathStr = '~/Demo/sampleDir'
var expandedSourcePathStr = $(sourcePathStr).stringByStandardizingPath.js
//Result: "/Users/adkj/Demo/sampleDir"
// function sourceFolder
var sourceFolder = SystemEvents.aliases.byName(expandedSourcePathStr)
//Result: Application("System Events").aliases.byName("/Users/adkj/Demo/sampleDir")
//function container
var container = sourceFolder.container()
//Result: Application("System Events").folders.byName("Macintosh HD:Users:adkj:Demo")
// String containerPath
var containerPath = container.path()
//Result: "Macintosh HD:Users:adkj:Demo"
// Create an array of items functions to be processed
var items = SystemEvents.aliases.byName(sourcePathStr).diskItems
items[1]()
//Result: Path("/Users/adkj/Demo/sampleDir/samplefile.rtf")
items[1].class()
//Result: "file"
items[1].name()
//Result: "samplefile.rtf"
items[0].path()
//Result: "Macintosh HD:Users:adkj:Demo:sampleDir:.DS_Store";
文件增删
js">var app = Application.currentApplication()
app.includeStandardAdditions = true
var Finder = Application('Finder')
var SystemEvents = Application('System Events')
// Ask a folder to process
var sourcePathStr = '~/Demo/sampleDir'
//Expands ~ to be full user path
var expandedSourcePathStr = $(sourcePathStr).stringByStandardizingPath.js
// Allows for running the script multiple times
var duplicateAvoider = Date.now().toString()
// function sourceFolder
var sourceFolder = SystemEvents.aliases.byName(expandedSourcePathStr)
//Result: Application("System Events").aliases.byName("/Users/adkj/Demo/sampleDir")
// Create the destination folder
//function container
var container = sourceFolder.container()
//Result: Application("System Events").folders.byName("Macintosh HD:Users:adkj:Demo")
// String containerPath
var containerPath = container.path()
//Result: "Macintosh HD:Users:adkj:Demo"
// .make is function/method from Standard Suite
var destinationFolder = Finder.make({
new: 'folder',
at: containerPath,
withProperties: {
name: sourceFolder.name() + ' - New',
},
})
// Create an array of items functions to be processed
var items = SystemEvents.aliases.byName(sourcePathStr).diskItems
items[0]()
//Result: Path("/Users/adkj/Demo/sampleDir/samplefile.rtf")
items[0].class()
//Result: "file"
items[0].name()
//Result: "samplefile.rtf"
items[1].path()
//Result: "Macintosh HD:Users:adkj:Demo:sampleDir:.DS_Store"
//Finder.move("Macintosh HD:Users:adkj:Demo:sampleDir:samplefile.rtf", { to: "Macintosh HD:Users:adkj:Demo:sampleDir - New" });
reveal 文件夹
js">// Script to Open a Finder Window with a folder and bring to the front
var Finder = Application('Finder')
var demoDir = '~/Desktop'
//Resolves the ~ to be the home directory path
var fullDemoDir = $(demoDir).stringByStandardizingPath.js
//Opens Finder window containing ~/Desktop
// Brings Path object into view
Finder.reveal(Path(fullDemoDir))
//Brings finder to front
Finder.activate()
等价于
js">var Finder = Application('Finder')
Finder.reveal(Path('/Users/luomingyang/Desktop'))
Finder.activate()
语音反馈
js">// Loads Apple's UI elements and others.
app = Application.currentApplication()
app.includeStandardAdditions = true
dialogResult = app.displayDialog('Please enter your name', {
withTitle: 'Greetings',
defaultAnswer: '',
buttons: ['Goodbye', 'Hello'],
defaultButton: 2,
})
if (dialogResult.buttonReturned == 'Hello') {
app.say('Hello ' + dialogResult.textReturned)
} else {
app.say('Goodbye' + dialogResult.textReturned)
}
执行 shell 命令
js">;(() => {
'use strict'
// evalAS :: String -> IO String
const evalAS = (s) => {
const a = Application.currentApplication(),
sa = ((a.includeStandardAdditions = true), a)
return sa.doShellScript(
['osascript -l AppleScript <<OSA_END 2>/dev/null']
.concat([s])
.concat('OSA_END')
.join('\n')
)
}
return evalAS('say "hello world"')
})()
cocoa
当有应用启动时,则显示弹窗hello app.name
js">var me = Application.currentApplication()
me.includeStandardAdditions = true
ObjC.import('Cocoa')
ObjC.registerSubclass({
name: 'MainController',
methods: {
'appDidLaunch:': {
types: ['void', ['id']],
implementation: function (notification) {
var appName = notification.userInfo.objectForKey('NSApplicationName').js
me.activate()
me.displayAlert(`Hello, ${appName}!`, { message: 'Nice to meet you.' })
Application(appName).activate()
},
},
'screensaverDidStop:': {
types: ['void', ['id']],
implementation: function (notification) {
me.activate()
me.displayAlert('Goodbye, screensaver!', {
message: 'It was nice knowing you.',
})
},
},
},
})
var controller = $.MainController.new
$.NSWorkspace.sharedWorkspace.notificationCenter.addObserverSelectorNameObject(
controller,
'appDidLaunch:',
$.NSWorkspaceDidLaunchApplicationNotification,
undefined
)
$.NSDistributedNotificationCenter.defaultCenter.addObserverSelectorNameObject(
controller,
'screensaverDidStop:',
'com.apple.screensaver.didstop',
undefined
)
移动鼠标到屏幕中央
js">ObjC.import('stdlib')
ObjC.import('CoreGraphics')
// Move mouse cursor to specified position
function moveMouse(x, y) {
var pos = $.CGPointMake(x, y)
var event = $.CGEventCreateMouseEvent(
null,
$.kCGEventMouseMoved,
pos,
$.kCGMouseButtonLeft
)
$.CGEventPost($.kCGHIDEventTap, event)
}
// Run script
function run() {
var app = Application.currentApplication()
app.includeStandardAdditions = true
var win = app.windows[0],
bounds = win.properties().bounds
// Calculate centre of window
var x = Math.trunc(bounds.x + bounds.width / 2),
y = Math.trunc(bounds.y + bounds.height / 2)
console.log('centre = ' + x + 'x' + y)
moveMouse(x, y)
}
鼠标点击特定坐标点
js">;(() => {
// mouseclick(); // Clicks at mouse location
mouseclick(89, 74) // Clicks at coordinates {35, 60}
// relative to top-left of screen
})()
function mouseclick(x = null, y = null) {
ObjC.import('Cocoa')
const nil = $()
const mouseLoc = $.NSEvent.mouseLocation
const screenH = 1920
mouseLoc.y = screenH - mouseLoc.y
var coords = mouseLoc
if (x && y) coords = { x: x, y: y }
var mousedownevent = $.CGEventCreateMouseEvent(
nil,
$.kCGEventLeftMouseDown,
coords,
nil
)
var mouseupevent = $.CGEventCreateMouseEvent(
nil,
$.kCGEventLeftMouseUp,
coords,
nil
)
$.CGEventPost($.kCGHIDEventTap, mousedownevent) //鼠标按下
$.CGEventPost($.kCGHIDEventTap, mouseupevent) //鼠标松开
}
使用 C 创建应用
利用 JavaScript 构建 OSX 应用_hao741100265 的专栏-CSDN 博客
js">ObjC.import('Cocoa')
// 实际运行时脚本会崩溃,原因未知
// 添加窗口的功能,有标题,关闭按钮,最小化按钮,|(=or)表示添加多个
var styleMask =
$.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask
var windowHeight = 85
var windowWidth = 600
var ctrlsHeight = 80
var minWidth = 400
var minHeight = 340
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
$.NSMakeRect(0, 0, windowWidth, windowHeight),
styleMask,
$.NSBackingStoreBuffered,
false
)
window.center
window.title = 'Choose and Display Image'
window.makeKeyAndOrderFront(window)
参考文献
OS X 10.11 官方文档 > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l3esg3K0-1612578945293)(https://files.catbox.moe/ct4ncl.png)] >Home · JXA-Cookbook/JXA-Cookbook Wiki > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hHYOSkJ-1612578945295)(https://files.catbox.moe/4ktfms.png)] >jxa@apple-dev.groups.io | Wiki > User Interaction with Files and Folders · JXA-Cookbook/JXA-Cookbook Wiki > Angelina Fabbro - OSX Automation Using JavaScript - JSConfUY 2015 - YouTube > 在 JXA 中使用 Objective C(ObjC)·JXA-Cookbook / JXA-Cookbook Wiki