2021-02-06

news/2024/7/19 12:59:21 标签: js, javascript, gui, macos
  • JXA-ObjC
    • $
    • 引入框架
    • 转换 ObjC 的语法
    • 判断是否是文件夹
  • demo
    • 新建终端窗口
    • 在 finder 右键状态栏
    • 通过 apple 脚本的形式解析并运行 js 文件
    • 如何转换报错信息
    • doShell
    • 将文件复制到剪贴板
    • 打开文件选择器
      • 文件选择框 var require = function (path) {
      • 获得指定目录下的所有文件列表
    • 获取指定文件夹下的文件信息
    • 网络请求
    • 准备
      • 在 safari 中调试脚本
      • 在终端中运行 JavaScript
    • 词典
      • System Events
        • keystrokes
        • 剪贴板
        • 网络连接
        • 菜单栏
        • 操作 UI 元素
        • 对象结构
      • mail
      • 日历
        • 元素结构
        • code
    • 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()

weChat

使用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

mail

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
          • elements
            • attendee
              • email(get)
              • displayName(get)
              • participationStatus(get)[是否接受邀约]
            • display alarm
            • mail alarm
            • open file alarm
            • sound alarm
        • commands
          • show()
    • documents
    • windows

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


http://www.niftyadmin.cn/n/1482397.html

相关文章

iOS音视频项目开发(跨平台)

苹果手机带动了IOS的火热&#xff0c;一大堆开发人员在捣鼓IOS平台的开发&#xff0c;相信大家也使用过QQ的语音视频对话功能&#xff0c;但是不知道大家有没有试过自己来开发一个基于IOS平台的音视频即时通讯的应用&#xff0c;这个应用必须能够做到跨平台。现在介绍两种方法使…

NOI2019:Stay at Home

7.16 NOI D1 从同步赛开始更起好了 先看了一圈题目&#xff0c;发现T1非常可做&#xff0c;二次函数因为对称轴在\(x < 0\)的地方所以有跟一次函数类似的单调性&#xff0c;搞个单调队列维护一下似乎就可以了。大力码码码&#xff0c;过了大样例就没管了也没有拍。 然后T2和…

cocos2d-x 之 CCProgressTimer

--绕圆心转动的进度动画 local function SpriteProgressToRadial() local leftProgress CCProgressTimer:create(CCSprite:create("circle.png")) leftProgress:setType(kCCProgressTimerTypeRadial) --扇形顺时针形式 leftProgress:setPosition(…

使用JXA 实现Photoshop 新建文档

直接先上代码 #!/usr/bin/env osascript -l JavaScriptlet doc new Application(Adobe Photoshop 2021).Document({name: Some title for a new doc, }) doc.make()不得不说&#xff0c;苹果官方对jxa的文档支持太简陋了&#xff0c;下图是词典文件 但是没有实例演示&#x…

Linux命令学习-mkdir命令

Linux中&#xff0c;mkdir命令的全称是make directory&#xff0c;即创建目录的意思。 假设当前处于wintest用户的主目录&#xff0c;路径为 /home/wintest &#xff0c;存在文件夹testA&#xff0c;进入testA 在testA目录下&#xff0c;新建文件夹testBmkdir testB当前用户主目…

【工作中的Python】随机点名小脚本

背景&#xff1a;项目组每周的例会中&#xff0c;有一项固定内容就是技术分享&#xff0c;可以是与工作相关或无关的任何技术主题。进行技术分享讲解的同学是随机抽签的。由此做了一个Python的小脚本用于抽取姓名。 脚本内容如下&#xff1a; #!/usr/bin/pythonimport os impor…

specpu2006免费

https://github.com/cloudharmony/speccpu2006 转载于:https://www.cnblogs.com/xuanbjut/p/11185310.html

关于Photoshop的JXA、UXP文档整理

JXA Photoshop 属性 doJavascriptbackgroundColor(set)foregroundColor(set)currentDocument(set)currentTool(set)displayDialogs(set)macintoshFileTypessystemInformation()menusettings(set)fontsphotoshopOpenDialogbatchchangeModeconvertToShapecropexportflipCanvas注意…