An open-source userscript editor for Safari
Userscripts is available for iOS (+ipadOS) and macOS. For all versions, installation is done through Apple's App Store. On macOS, versions prior to 4.x
were made available to download and install directly from the repository, but due to changes in the way Apple allows developers to distribute apps built with the WebExtension API, that is no longer an option.
To run Userscripts on iOS you should be on iOS 15.1 or higher.
To run Userscripts on macOS you should running macOS 12 or higher, along with Safari 14.1 or higher.
It's recommend to read this documentation and, if you have time, watch the following video overviews to familiarize yourself with the app and extension.
Once the app is downloaded and installed the following steps should be taken:
iOS
Settings > Extensions > Userscripts
on
All Websites
Always Allow
Userscripts for All Websites
macOS
After installing Userscripts on macOS, you do not need to select a userscripts directory if you do not plan on syncing your userscripts between multiple devices. Instead you can choose to use the default directory, which is located at ~/User/Library/Containers/Userscripts/Data/Documents/scripts
- again, this is default (and automatic) behavior. You only need to select a new location if you want to store your userscripts elsewhere, which is especially useful if you are using an external code editor such as Sublime Text or VSCode.
settings
button (represented by a cog) displays the settings modal (discussed below)plus
button allows users to add new items
New CSS
is a "userscript" that expects CSS codeNew Javascript
is a prototypical userscript that expects Javascript codeNew Remote
allows the user to add a remote hosted userscript (or style) by inputting the web address (ex: https://www.k21p.com/example.user.js
)@version
tag@updateURL
tagDiscard
- while editing, reverts any unsaved changes you've made to a userscriptSave
- while editing, saves all changes you've made to a userscript
Command + S
is the keyboard shortcut for the action() [] {} "" ''
4
because using 2
spaces is absolute insanity@match
patterns, a page url that matches against a pattern in this list will be ignored for script injectionUserscripts Safari currently supports the following userscript metadata:
@name
- This will be the name that displays in the sidebar and be used as the filename - you can not use the same name for multiple files of the same type@description
- Use this to describe what your userscript does - this will be displayed in the sidebar - there is a setting to hide descriptions@icon
- This doesn't have a function with this userscript manager, but the first value provided in the metadata will be accessible in the GM_/GM.info
object@match
- Domain match patterns - you can use several instances of this field if you'd like multiple domain matches - view this article for more information on constructing patterns
http/s
@exclude-match
- Domain patterns where you do not want the script to run@include
- Used to match against urls for injection, globs and regular expressions are allowed, read more here@exclude
- Functions in a similar way as @include
but rather than injecting, a match against this key's value will prevent injection@inject-into
- allows the user to choose which context to inject the script into
auto
(default), content
, page
GM
apis are only available when using content
@run-at
@weight
@require
@version
@updateURL
, this will allow the user to update a userscript from a remote source, if the version on their machine is <
version at the update URL@version
does nothing by itself, it needs to be paired with@updateURL
for remote updating to function properly@updateURL
>
the version on the local machine, the file will be updated@updateURL
does nothing by itself, it needs to be paired with @version
for remote updating to function properly@downloadURL
@version
and @updateURL
)@version
and @updateURL
, if the local version is <
the version of the file that @updateURL
points to, the extension will attempt to update the file's code with the contents of the file located at the @downloadURL
@downloadURL
does nothing by itself, it needs @version
and @updateURL
to present in order to function properly@noframes
All userscripts need at least 1 @match
or @include
to run!
Userscripts currently supports the following api methods. All methods are asynchronous unless otherwise noted. Users must @grant
these methods in order to use them in a userscript. When using API methods, it's only possible to inject into the content script scope due to security concerns.
GM.addStyle(css)
css: String
GM.setValue(key, value)
key: String
, value: Any
GM.getValue(key, defaultValue)
key: String
, defaultValue: Any
GM.deleteValue(key)
key: String
GM.listValues()
GM.getTab()
Any
data that is persistent as long as this tab is openGM.saveTab(tabObj)
tabObj: Any
GM.openInTab(url, openInBackground)
url: String
, openInBackground: Bool
GM.closeTab(tabId)
tabId: Int
tabId
is optional and if omitted the tab that called US.closeTab
will be closedGM.setClipboard(data, type)
data: String
- requiredtype: String
- optional and defaults to text/plain
Bool
indicating successGM.info
&& GM_info
@grant
scriptHandler: String
- returns Userscripts
version: String
- the version of Userscripts appscriptMetaStr: String
- the metablock for the currently running scriptscript: Object
- contains data about the currently running script
description: String
exclude-match: [String]
excludes: [String]
grant: [String]
includes: [String]
inject-into: String
matches: [String]
name: String
namespace: String
noframes: Bool
require: [String]
resources: [String]
- currently not implementedrun-at: String
version: String
- the userscript version valueGM.xmlHttpRequest(details)
details: Object
details
object accepts the following properties
url
- String
- requiredmethod
- String
- defaults to GET
user
- String
password
- String
headers
- Object
overrideMimeType
timeout
- Int
binary
- Bool
data
- String
responseType
- String
onabort
- function
onerror
- function
onload
- function
onloadend
- function
onloadstart
- function
onprogress
- function
onreadystatechange
- function
ontimeout
- function
readyState
response
responseHeaders
responseType
responseURL
status
statusText
timeout
withCredentials
responseText
(when responseType
is text
)abort
, which is a function
const foo = GM.xmlHttpRequest({...});
... foo.abort()
to abort the requestGM_xmlhttpRequest(details)
GM.xmlHttpRequest
, works exactly the sameThis is the directory where the app/extension will read from and write to. This directory is changed by opening the containing app and clicking the respective "change location" button.
Script Directory Notes
If you encounter a problem while using this app/extension or are in need of some assistance, please open an issue here in the repository. When doing so, please provide as much detail as possible. This includes listing system specs and what website and script you are trying to execute. Please follow the issue template!
"Refused to execute a script" error(s), what should I do!?
You are seeing this error because of the website's Content Security Policy. Currently there is no way to allow extension content scripts to bypass CSPs in Safari.
Automatically, the extension will attempt to circumvent strict CSPs, but if you are still experiencing issues, trying setting the userscript metadata key/val
// @inject-into auto
or// @inject-into content
.You can read more about this in this issue.
Do I need to use the extension's editor to create new userscripts or to edit existing?
You can use your own editor to update and manage your files. As long as you are saving the files to the save location, and they are properly formatted, they should be injected. However, you must open the extension popup beforehand. That means, if you create a new or edit an existing userscript with an external editor and save it to the save location, before injection will occur properly, the extension popup must be opened and the popup must load completely.
What are the keyboard shortcuts?
Whilst using the included editor, clicking
⌘ + s
will save the file. While working the editor, clicking⌘ + f
will bring up the search bar andesc
will hide it.
When I use @require
, where are the required files stored?
All required files are saved as Javascript files in the extension container folder in macOS 11.x. That folder is located in the default save location, at:
~/Library/Containers/Userscripts/Data/Documents/require/
.If you move files from the require folder or manually edit the
manifest.json
file, you will likely break app/extension functionality.
Code level contributions are welcome. I prefer to collaborate directly with contributors rather than receiving spontaneous pull requests. If you feel you can improve the project in some way, please reach out to me by email or by opening an issue with your improvement/feature request.
Further, any issue marked "help wanted" is actively seeking assistance. Please respond to those issues with feedback, guidance or offers of coding assistance.
Notes:
develop
branch for your contributionsPlease ensure your contributions align with the project's license before committing anything.
The quickest and easiest way to support the project is by leaving a positive review on the App Store if you enjoy the extension and want to see future improvements. Seeing these reviews let me know I am doing something right, or wrong, and motivates me to continue working on the project.
The second best way to help out is to sign up to beta test new versions of the app. Since this extension values your privacy, and does not collect any data from users, it is difficult to gauge how the extension is being used. By signing up to be a beta tester it not only allows you to test upcoming features, but also gives me the opportunity to elicit direct feedback from real users.
Userscripts does not collect any data from its users nor monitor activities or actions you perform within the application and extension. This means everything that you do with the application and extension is private to you and is never shared with the developers or third parties. Since there is no data collection, there is no data retention of any kind.
Copyright (c) 2022 Justin Wasack
Licensed under the GNU General Public License v3.0 license for all open source applications. A commercial license is required for all other applications.