├── .gitignore ├── LICENCE.txt ├── README.md ├── SafariActivate.js ├── SafariClose.js ├── SafariCurrentTab.js ├── SafariRunJS.js ├── SafariTabs.js ├── cloud ├── tabs.go └── tabs_test.go ├── cmd └── safari │ ├── cloud.go │ ├── history.go │ └── main.go ├── go.mod ├── go.sum ├── history ├── history.go └── history_test.go ├── js.go ├── modd.conf ├── safari.1 ├── safari.go ├── safari_test.go ├── tabs.go └── tabs_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /vendor 3 | 4 | # Created by https://www.gitignore.io/api/sublimetext,vim,go 5 | 6 | ### SublimeText ### 7 | # cache files for sublime text 8 | *.tmlanguage.cache 9 | *.tmPreferences.cache 10 | *.stTheme.cache 11 | 12 | # workspace files are user-specific 13 | *.sublime-workspace 14 | 15 | # project files should be checked into the repository, unless a significant 16 | # proportion of contributors will probably not be using SublimeText 17 | # *.sublime-project 18 | 19 | # sftp configuration file 20 | sftp-config.json 21 | 22 | # Package control specific files 23 | Package Control.last-run 24 | Package Control.ca-list 25 | Package Control.ca-bundle 26 | Package Control.system-ca-bundle 27 | Package Control.cache/ 28 | Package Control.ca-certs/ 29 | bh_unicode_properties.cache 30 | 31 | # Sublime-github package stores a github token in this file 32 | # https://packagecontrol.io/packages/sublime-github 33 | GitHub.sublime-settings 34 | 35 | 36 | ### Vim ### 37 | # swap 38 | [._]*.s[a-w][a-z] 39 | [._]s[a-w][a-z] 40 | # session 41 | Session.vim 42 | # temporary 43 | .netrwhist 44 | *~ 45 | # auto-generated tag files 46 | tags 47 | 48 | 49 | ### Go ### 50 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 51 | *.o 52 | *.a 53 | *.so 54 | 55 | # Folders 56 | _obj 57 | _test 58 | 59 | # Architecture specific extensions/prefixes 60 | *.[568vq] 61 | [568vq].out 62 | 63 | *.cgo1.go 64 | *.cgo2.c 65 | _cgo_defun.c 66 | _cgo_gotypes.go 67 | _cgo_export.* 68 | 69 | _testmain.go 70 | 71 | *.exe 72 | *.test 73 | *.prof 74 | 75 | # deanishe's stuff 76 | /.remarkrc 77 | /safari 78 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dean Jackson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | go-safari 3 | ========= 4 | 5 | Read-only access to Safari bookmarks, Reading List and history, plus live interaction with windows and tabs. 6 | 7 | macOS only (tested on Sierra and High Sierra). 8 | 9 | See [godoc][godoc] for documentation. 10 | 11 | Licensing 12 | --------- 13 | 14 | This code is released under the [MIT License][mit]. 15 | 16 | [godoc]: https://godoc.org/pkg/github.com/deanishe/go-safari 17 | [mit]: ./LICENCE.txt 18 | -------------------------------------------------------------------------------- /SafariActivate.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | // 3 | // Copyright (c) 2016 Dean Jackson 4 | // 5 | // MIT Licence. See http://opensource.org/licenses/MIT 6 | // 7 | // Created on 2016-05-30 8 | // 9 | 10 | ObjC.import('stdlib') 11 | 12 | var safari = Application('Safari') 13 | safari.includeStandardAdditions = true 14 | 15 | // activateWindow | Activate Safari and bring the specified window to the front. 16 | function activateWindow(winIdx) { 17 | var win = safari.windows[winIdx-1]() 18 | 19 | if (winIdx != 1) { 20 | win.visible = false 21 | win.visible = true 22 | } 23 | 24 | safari.activate() 25 | } 26 | 27 | // activateTab | Activate Safari, bring window to front and make specified tab active. 28 | function activateTab(winIdx, tabIdx) { 29 | 30 | try { 31 | var win = safari.windows[winIdx-1]() 32 | } 33 | catch (e) { 34 | console.log('Invalid window: ' + winIdx) 35 | $.exit(1) 36 | } 37 | 38 | if (tabIdx == 0) { // Activate window 39 | activateWindow(winIdx) 40 | return 41 | } 42 | 43 | // Find tab to activate 44 | try { 45 | var tab = win.tabs[tabIdx-1]() 46 | } 47 | catch (e) { 48 | console.log('Invalid tab for window ' + winIdx + ': ' + tabIdx) 49 | $.exit(1) 50 | } 51 | 52 | // Activate window and tab if it's not the current tab 53 | activateWindow(winIdx) 54 | if (!tab.visible()) { 55 | win.currentTab = tab 56 | } 57 | 58 | } 59 | 60 | // run | CLI entry point 61 | function run(argv) { 62 | var win = 0, 63 | tab = 0; 64 | 65 | win = parseInt(argv[0], 10) 66 | if (argv.length > 1) { 67 | tab = parseInt(argv[1], 10) 68 | } 69 | 70 | if (isNaN(win)) { 71 | console.log('Invalid window: ' + win) 72 | $.exit(1) 73 | } 74 | 75 | if (isNaN(tab)) { 76 | console.log('Invalid tab: ' + tab) 77 | $.exit(1) 78 | } 79 | 80 | activateTab(win, tab) 81 | } -------------------------------------------------------------------------------- /SafariClose.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | // 3 | // Copyright (c) 2016 Dean Jackson 4 | // 5 | // MIT Licence. See http://opensource.org/licenses/MIT 6 | // 7 | // Created on 2016-05-30 8 | // 9 | 10 | Array.prototype.contains = function(o) { 11 | return this.indexOf(o) > -1 12 | } 13 | 14 | ObjC.import('stdlib'); 15 | 16 | // Permissible targets 17 | var whats = ['win', 'tab', 'tabs-other', 'tabs-left', 'tabs-right'], 18 | app = Application('Safari'); 19 | app.includeStandardAdditions = true; 20 | 21 | // usage | Print help to STDOUT 22 | function usage() { 23 | 24 | console.log('SafariClose.js (win|tab|tabs-other|tabs-left|tabs-right) [] []'); 25 | console.log(''); 26 | console.log('Close specified window and/or tab(s). If not specified, and '); 27 | console.log('default to the frontmost window and current tab respectively.'); 28 | console.log(''); 29 | console.log('Usage:'); 30 | console.log(' SafariClose.js win []'); 31 | console.log(' SafariClose.js (tab|tabs-other|tabs-left|tabs-right) [] []'); 32 | console.log(' SafariClose.js -h'); 33 | console.log(''); 34 | console.log('Options:'); 35 | console.log(' -h Show this help message and exit.'); 36 | 37 | } 38 | 39 | // closeWindow | Close the specified Safari window 40 | function closeWindow(winIdx) { 41 | var win = app.windows[winIdx-1]; 42 | win.close(); 43 | } 44 | 45 | // getCurrentTab | Return the index of the current tab of frontmost window 46 | function getCurrentTab() { 47 | return app.windows[0].currentTab.index() 48 | } 49 | 50 | // closeTabs | tabFunc(idx, tab) is called for each tab in the window. 51 | // Tab is closed if it returns true. 52 | function closeTabs(winIdx, tabFunc) { 53 | 54 | var win = app.windows[winIdx-1], 55 | tabs = win.tabs, 56 | current = win.currentTab, 57 | toClose = []; 58 | 59 | // Loop backwards, so tab indices don't change as we close them 60 | for (i=tabs.length-1; i>-1; i--) { 61 | var tab = tabs[i]; 62 | if (tabFunc(i+1, tab)) { 63 | console.log('Closing tab ' + (i+1) + ' ...'); 64 | tab.close(); 65 | } 66 | } 67 | 68 | } 69 | 70 | function run(argv) { 71 | var what = argv[0], 72 | winIdx = 1, // default to frontmost window 73 | tabIdx = 0; 74 | 75 | if (argv.contains('-h') || argv.contains('--help')) { 76 | usage(); 77 | $.exit(0); 78 | } 79 | 80 | // Validate arguments 81 | if (!whats.contains(what)) { 82 | console.log('Invalid target: ' + what); 83 | console.log(''); 84 | usage(); 85 | $.exit(1); 86 | } 87 | 88 | if (typeof(argv[1]) != 'undefined') { 89 | winIdx = parseInt(argv[1], 10); 90 | if (isNaN(winIdx)) { 91 | console.log('Invalid window number: ' + argv[1]); 92 | $.exit(1); 93 | } 94 | } 95 | 96 | if (what != 'win') { 97 | if (typeof(argv[2]) != 'undefined') { 98 | var tabIdx = parseInt(argv[2], 10); 99 | if (isNaN(tabIdx)) { 100 | console.log('Invalid tab number for window ' + winIdx + ': ' + argv[2]); 101 | $.exit(1); 102 | } 103 | } else { 104 | tabIdx = getCurrentTab(); 105 | } 106 | } 107 | 108 | console.log('winIdx=' + winIdx + ', tabIdx=' + tabIdx); 109 | 110 | // Let's close some shit 111 | if (what == 'win') { 112 | 113 | return closeWindow(winIdx) 114 | 115 | } else if (what == 'tab') { 116 | 117 | //return closeTab(winIdx, tabIdx) 118 | return closeTabs(winIdx, function(i, t) { 119 | return i === tabIdx 120 | }) 121 | 122 | } else if (what == 'tabs-other') { 123 | 124 | return closeTabs(winIdx, function(i, t) { 125 | return i != tabIdx 126 | }) 127 | 128 | } else if (what == 'tabs-left') { 129 | 130 | return closeTabs(winIdx, function(i, t) { 131 | return i < tabIdx 132 | }) 133 | 134 | 135 | } else if (what == 'tabs-right') { 136 | 137 | return closeTabs(winIdx, function(i, t) { 138 | return i > tabIdx 139 | }) 140 | 141 | } 142 | } -------------------------------------------------------------------------------- /SafariCurrentTab.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | // 4 | // Copyright (c) 2016 Dean Jackson 5 | // 6 | // MIT Licence. See http://opensource.org/licenses/MIT 7 | // 8 | // Created on 2016-11-04 9 | // 10 | 11 | /*************************************************************** 12 | Simple command-line program to list open tabs in Safari. 13 | ***************************************************************/ 14 | 15 | ObjC.import('stdlib') 16 | 17 | var safari = Application('Safari') 18 | safari.includeStandardAdditions = true 19 | 20 | 21 | function getCurrentTab() { 22 | var winIdx = 1, 23 | tab = safari.windows[0].currentTab, 24 | tabIdx = tab.index(), 25 | title = tab.name(), 26 | url = tab.url() 27 | console.log('win=1, tab=' + tabIdx + ', title="' + title + '", url=' + url) 28 | return {title: title, url: url, index: tabIdx, windowIndex: 1} 29 | } 30 | 31 | function run(argv) { 32 | return JSON.stringify(getCurrentTab()) 33 | } 34 | -------------------------------------------------------------------------------- /SafariRunJS.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | // 3 | // Copyright (c) 2017 Dean Jackson 4 | // 5 | // MIT Licence. See http://opensource.org/licenses/MIT 6 | // 7 | // Created on 2017-10-22 8 | // 9 | 10 | ObjC.import('stdlib') 11 | 12 | var safari = Application('Safari') 13 | safari.includeStandardAdditions = true 14 | 15 | 16 | // runJSInTab | Run JavaScript in tab 17 | function runJSInTab(winIdx, tabIdx, js) { 18 | 19 | try { 20 | var win = safari.windows[winIdx-1]() 21 | } 22 | catch (e) { 23 | console.log('Invalid window: ' + winIdx) 24 | $.exit(1) 25 | } 26 | 27 | try { 28 | var tab = win.tabs[tabIdx-1]() 29 | } 30 | catch (e) { 31 | console.log('Invalid tab for window ' + winIdx + ': ' + tabIdx) 32 | $.exit(1) 33 | } 34 | 35 | safari.doJavaScript(js, {in: tab}) 36 | } 37 | 38 | function run(argv) { 39 | var winIdx = 0, 40 | tabIdx = 0; 41 | 42 | if (argv.length != 3) { 43 | console.log('Usage: SafariRunJS.js