diff --git a/types/tampermonkey/index.d.ts b/types/tampermonkey/index.d.ts new file mode 100644 index 0000000000..80a49fbae0 --- /dev/null +++ b/types/tampermonkey/index.d.ts @@ -0,0 +1,397 @@ +// Type definitions for non-npm package Tampermonkey 4.x +// Project: https://tampermonkey.net +// Definitions by: Steven Wang +// Nikolay Borzov +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +// This definition is based on the API reference of Tampermonkey +// https://tampermonkey.net/documentation.php +// TypeScript Version: 3.3 + +declare namespace Tampermonkey { + type ValueChangeListener = ( + name: string, + oldValue: any, + newValue: any, + remote: boolean + ) => void; + + // Response + + enum ReadyState { + Unsent = 0, + Opened = 1, + HeadersReceived = 2, + Loading = 3, + Done = 4 + } + + interface ResponseBase { + readonly responseHeaders: string; + readonly readyState: ReadyState; + readonly response: any; + readonly responseText: string; + readonly responseXML: Document | null; + readonly status: number; + readonly statusText: string; + } + + interface ProgressResponseBase { + done: number; + lengthComputable: boolean; + loaded: number; + position: number; + total: number; + totalSize: number; + } + + interface ErrorResponse extends ResponseBase { + readonly error: string; + } + + interface Response extends ResponseBase { + readonly finalUrl: string; + readonly context: TContext; + } + + interface ProgressResponse + extends Response, + ProgressResponseBase {} + + // Request + + interface RequestHeaders { + readonly [header: string]: string; + } + + type RequestEventListener = ( + this: TResponse, + response: TResponse + ) => void; + + interface Request { + method?: 'GET' | 'HEAD' | 'POST'; + /** Destination URL */ + url: string; + /** + * i.e. user-agent, referer... (some special headers are not supported + * by Safari and Android browsers) + */ + headers?: RequestHeaders; + /** String to send via a POST request */ + data?: string; + /** Send the data string in binary mode */ + binary?: boolean; + /** Timeout in ms */ + timeout?: number; + /** Property which will be added to the response object */ + context?: TContext; + responseType?: 'arraybuffer' | 'blob' | 'json'; + /** MIME type for the request */ + overrideMimeType?: string; + /** Don't send cookies with the requests (please see the fetch notes) */ + anonymous?: boolean; + /** + * (Beta) Use a fetch instead of a xhr request(at Chrome this causes + * `xhr.abort`, `details.timeout` and `xhr.onprogress` to not work and + * makes `xhr.onreadystatechange` receive only readyState 4 events) + */ + fetch?: boolean; + /** Username for authentication */ + username?: string; + password?: string; + + // Events + + /** Callback to be executed if the request was aborted */ + onabort?(): void; + /** Callback to be executed if the request ended up with an error */ + onerror?: RequestEventListener; + /** Callback to be executed if the request started to load */ + onloadstart?: RequestEventListener>; + /** Callback to be executed if the request made some progress */ + onprogress?: RequestEventListener>; + /** Callback to be executed if the request's ready state changed */ + onreadystatechange?: RequestEventListener>; + /** Callback to be executed if the request failed due to a timeout */ + ontimeout?(): void; + /** Callback to be executed if the request was loaded */ + onload?: RequestEventListener>; + } + + // Download Response + + interface DownloadProgressResponse extends ProgressResponseBase { + readonly finalUrl: string; + } + + interface DownloadErrorResponse { + /** + * Error reason + * - `not_enabled` - the download feature isn't enabled by the user + * - `not_whitelisted` - the requested file extension is not + * whitelisted + * - `not_permitted` - the user enabled the download feature, but did + * not give the downloads permission + * - `not_supported` - the download feature isn't supported by the + * browser/version + * - `not_succeeded` - the download wasn't started or failed, the + * details attribute may provide more information + */ + error: + | 'not_enabled' + | 'not_whitelisted' + | 'not_permitted' + | 'not_supported' + | 'not_succeeded'; + /** Detail about that error */ + details?: string; + } + + // Download Request + + interface DownloadRequest { + /** URL from where the data should be downloaded */ + url: string; + /** + * Filename - for security reasons the file extension needs to be + * whitelisted at Tampermonkey options page + */ + name: string; + headers?: RequestHeaders; + /** Show 'Save As' dialog */ + saveAs?: boolean; + timeout?: number; + /** Callback to be executed if this download ended up with an error */ + onerror?: RequestEventListener; + /** Callback to be executed if this download finished */ + ontimeout?(): void; + /** Callback to be executed if this download finished */ + onload?(): void; + /** Callback to be executed if this download failed due to a timeout */ + onprogress?: RequestEventListener; + } + + interface AbortHandle { + abort(): TReturn; + } + + interface OpenTabOptions { + /** Decides whether the new tab should be focused */ + active?: boolean; + /** Inserts the new tab after the current one */ + insert?: boolean; + /** Makes the browser re-focus the current tab on close */ + setParent?: boolean; + } + + interface OpenTabObject { + /** Closes tab */ + close(): void; + /** Set closed listener */ + onclosed?(): void; + closed: boolean; + } + + interface NotificationThis extends Notification { + id: string; + } + + type NotificationOnClick = (this: NotificationThis) => void; + /** `clicked` is `true` when `text` was set */ + type NotificationOnDone = (this: NotificationThis, clicked: boolean) => void; + + interface Notification { + /** Text of the notification (optional if highlight is set) */ + text?: string; + /** Notification title. If not specified the script name is used */ + title?: string; + image?: string; + /** Flag whether to highlight the tab that sends the notification */ + highlight?: boolean; + /** Time after that the notification will be hidden. `0` = disabled */ + timeout?: number; + /** + * Called when the notification is closed (no matter if this was + * triggered by a timeout or a click) or the tab was highlighted + */ + onclick?: NotificationOnClick; + /** Called in case the user clicks the notification */ + ondone?: NotificationOnDone; + } + + interface TextNotification extends Notification { + /** Text of the notification (optional if highlight is set) */ + text: string; + } + + interface HighlightNotification extends Notification { + text?: undefined; + highlight: true; + } + + type NotificationDetails = TextNotification | HighlightNotification; +} + +/** + * The unsafeWindow object provides full access to the pages javascript + * functions and variables + */ +declare var unsafeWindow: Window; + +// Styles + +/** + * Adds the given style to the document and returns the injected style element. + */ +declare function GM_addStyle(css: string): HTMLStyleElement; + +// Storage + +/** Sets the value of `name` to the storage */ +declare function GM_setValue(name: string, value: any): void; + +/** + * Adds a change listener to the storage and returns the listener ID. + * The `remote` argument of the callback function shows whether this value was + * modified from the instance of another tab (`true`) or within this script + * instance (`false`). Therefore this functionality can be used by scripts of + * different browser tabs to communicate with each other. + * @param name Name of the observed variable + */ +declare function GM_addValueChangeListener( + name: string, + listener: Tampermonkey.ValueChangeListener +): number; + +/** Removes a change listener by its ID */ +declare function GM_removeValueChangeListener(listenerId: number): void; + +/** Gets the value of 'name' from storage */ +declare function GM_getValue( + name: string, + defaultValue?: TValue +): TValue; + +/** Deletes 'name' from storage */ +declare function GM_deleteValue(name: string): void; + +/** Lists all names of the storage */ +declare function GM_listValues(): string[]; + +// Resources + +/** Get the content of a predefined `@resource` tag at the script header */ +declare function GM_getResourceText(name: string): string; + +/** + * Get the base64 encoded URI of a predefined `@resource` tag at the script + * header + */ +declare function GM_getResourceURL(name: string): string; + +// Menu commands + +/** + * Register a menu to be displayed at the Tampermonkey menu at pages where this + * script runs and returns a menu command ID. + */ +declare function GM_registerMenuCommand( + name: string, + onClick: () => void, + accessKey?: string +): number; + +/** + * Unregister a menu command that was previously registered by + * `GM_registerMenuCommand` with the given menu command ID. + */ +declare function GM_unregisterMenuCommand(menuCommandId: number): void; + +// Requests + +/** Makes an xmlHttpRequest */ +declare function GM_xmlhttpRequest( + details: Tampermonkey.Request // tslint:disable-line:no-unnecessary-generics +): Tampermonkey.AbortHandle; + +/** Downloads a given URL to the local disk */ +declare function GM_download( + details: Tampermonkey.DownloadRequest +): Tampermonkey.AbortHandle; +declare function GM_download( + url: string, + name: string +): Tampermonkey.AbortHandle; + +// Tabs + +/** Saves the tab object to reopen it after a page unload */ +declare function GM_saveTab(obj: object): void; + +/** Gets a object that is persistent as long as this tab is open */ +declare function GM_getTab(callback: (obj: any) => void): void; + +/** Gets all tab objects as a hash to communicate with other script instances */ +declare function GM_getTabs( + callback: (tabsMap: { [tabId: number]: any }) => void +): void; + +// Utils + +/** Log a message to the console */ +declare function GM_log(...message: any[]): void; + +/** + * Opens a new tab with this url. + * The options object can have the following properties: + * - `active` decides whether the new tab should be focused, + * - `insert` that inserts the new tab after the current one and + * - `setParent` makes the browser re-focus the current tab on close. + * + * Otherwise the new tab is just appended. + * If `options` is boolean (loadInBackground) it has the opposite meaning of + * active and was added to achieve Greasemonkey 3.x compatibility. + * + * If neither active nor loadInBackground is given, then the tab will not be + * focused. + * @returns Object with the function `close`, the listener `onclosed` and a flag + * called `closed`. + */ +declare function GM_openInTab( + url: string, + options?: Tampermonkey.OpenTabOptions | boolean +): Tampermonkey.OpenTabObject; + +/** + * Shows a HTML5 Desktop notification and/or highlight the current tab. + * @param ondone If specified used instead of `details.ondone` + */ +declare function GM_notification( + details: Tampermonkey.NotificationDetails, + ondone?: Tampermonkey.NotificationOnDone +): void; + +/** + * Shows a HTML5 Desktop notification and/or highlight the current tab. + * @param text Text of the notification + * @param title Notification title. If not specified the script name is used + * @param onclick Called in case the user clicks the notification + */ +declare function GM_notification( + text: string, + title?: string, + image?: string, + onclick?: Tampermonkey.NotificationOnClick +): void; + +/** + * Copies data into the clipboard. + * The parameter 'info' can be an object like + * `{ type: 'text', mimetype: 'text/plain'}` or just a string expressing the + * type ("text" or "html"). + */ +declare function GM_setClipboard( + data: string, + info?: string | { type?: string; mimetype?: string } +): void; diff --git a/types/tampermonkey/tampermonkey-tests.ts b/types/tampermonkey/tampermonkey-tests.ts new file mode 100644 index 0000000000..445a72dbe9 --- /dev/null +++ b/types/tampermonkey/tampermonkey-tests.ts @@ -0,0 +1,334 @@ +// unsafeWindow + +let title: string = unsafeWindow.document.title; + +// GM_addStyle + +const scriptTag: HTMLStyleElement = GM_addStyle('a { font-wight: bold }'); + +// GM_setValue + +interface AppState { + form: { name: string }; +} + +GM_setValue('a', 'foobar'); +GM_setValue('b', 123); +GM_setValue('c', true); +GM_setValue('d', { form: { name: 'Bob' } }); + +// GM_addValueChangeListener + +GM_addValueChangeListener( + 'a', + (name: string, oldValue: string, newValue: string, remote: boolean) => {} +); +GM_addValueChangeListener( + 'b', + (name, oldValue: number, newValue: number, remote) => {} +); +GM_addValueChangeListener( + 'c', + (name, oldValue: boolean, newValue: boolean, remote) => {} +); +const dValueChangeListenerId = GM_addValueChangeListener( + 'd', + (name, oldValue: AppState, newValue: AppState, remote) => {} +); + +// GM_removeValueChangeListener + +GM_removeValueChangeListener(dValueChangeListenerId); + +// GM_getValue + +const a: string = GM_getValue('a', 'foobar'); +const b: number = GM_getValue('b', 123); +const c: boolean = GM_getValue('c', true); +const d: any = GM_getValue('d', null); +const e: string = GM_getValue('e'); +const f: number = GM_getValue('f'); +const g: boolean = GM_getValue('g'); +const h: AppState = GM_getValue('h'); + +// GM_deleteValue + +GM_deleteValue('d'); + +// GM_listValues + +GM_listValues().forEach((name: string) => { + console.log(name + ':', GM_getValue(name)); +}); + +// GM_getResourceText + +const template: string = GM_getResourceText('template'); + +// GM_getResourceURL + +const templateURL: string = GM_getResourceURL('template'); + +// GM_registerMenuCommand + +GM_registerMenuCommand('Hello, world (simple)', () => { + GM_log('Hello, world (simple) clicked'); +}); +const commandId = GM_registerMenuCommand( + 'Hello, world!', + () => { + GM_log('Hello, world clicked'); + }, + 'h' +); + +// GM_unregisterMenuCommand + +GM_unregisterMenuCommand(commandId); + +// GM_xmlhttpRequest + +// Bare Minimum +const abortHandle = GM_xmlhttpRequest({ + method: 'GET', + url: 'http://www.example.com/', + onload(response) { + alert(response.responseText); + } +}); + +abortHandle.abort(); + +// GET request +GM_xmlhttpRequest({ + method: 'GET', + url: 'http://www.example.net/', + headers: { + 'User-Agent': 'Mozilla/5.0', + Accept: 'text/xml' + }, + onload(response) { + let responseXML = response.responseXML; + // Inject responseXML into existing Object (only appropriate for XML content). + if (!responseXML) { + responseXML = new DOMParser().parseFromString( + response.responseText, + 'text/xml' + ); + } + + GM_log( + [ + response.status, + response.statusText, + response.readyState, + response.responseHeaders, + response.responseText, + response.finalUrl, + responseXML + ].join('\n') + ); + } +}); + +// POST request +GM_xmlhttpRequest({ + method: 'POST', + url: 'http://www.example.net/login', + data: 'username=johndoe&password=xyz123', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + onload(response) { + if (response.responseText.indexOf('Logged in as') > -1) { + location.href = 'http://www.example.net/dashboard'; + } + } +}); + +// HEAD request +GM_xmlhttpRequest({ + url: 'http://www.example.com', + method: 'HEAD', + onload(response) { + GM_log(response.responseHeaders); + } +}); + +// All options +interface RequestContext { + form: { + name: string + }; +} + +GM_xmlhttpRequest({ + method: 'POST', + url: 'http://example.com/', + headers: { 'User-Agent': 'greasemonkey' }, + data: 'foo=1&bar=2', + binary: false, + timeout: 10, + context: { + form: { + name: 'Alice' + } + }, + responseType: 'json', + overrideMimeType: 'text/plain', + anonymous: false, + username: 'guest', + password: 'abc123', + onabort() {}, + onerror(response) { + GM_log(response.error); + }, + onloadstart(response) { + GM_log(response.context.form.name); + }, + onload(response) { + GM_log(response.context.form.name); + }, + onprogress(response) { + GM_log(response.context.form.name, response.loaded); + }, + onreadystatechange(response) { + GM_log(response.context.form.name); + }, + ontimeout() {} +}); + +// Responses +GM_xmlhttpRequest({ + method: 'GET', + url: 'http://example.com/', + onload: response => { + const readyState: number = response.readyState; + const responseHeaders: string = response.responseHeaders; + const responseText: string = response.responseText; + const status: number = response.status; + const statusText: string = response.statusText; + const context: any = response.context; + const finalUrl: string = response.finalUrl; + }, + onprogress: response => { + const status: number = response.status; + const lengthComputable: boolean = response.lengthComputable; + const loaded: number = response.loaded; + const total: number = response.total; + } +}); + +// GM_download + +const downloadHandle = GM_download({ + url: 'http://tampermonkey.net/crx/tampermonkey.xml', + name: 'tampermonkey.xml', + headers: { 'User-Agent': 'greasemonkey' }, + saveAs: true, + timeout: 3000, + onerror(response) { + GM_log(response.error, response.details); + }, + ontimeout() {}, + onload() {}, + onprogress(response) { + GM_log(response.finalUrl, response.loaded, response.total); + } +}); + +downloadHandle.abort(); + +GM_download('http://tampermonkey.net/crx/tampermonkey.xml', 'tampermonkey.xml'); + +// GM_saveTab + +interface TabState { + form: { name: string }; +} + +const tabState: TabState = { + form: { + name: 'Alice' + } +}; + +GM_saveTab(tabState); + +// GM_getTab + +GM_getTab((savedTabState: TabState) => { + GM_log(savedTabState.form.name); +}); + +// GM_getTabs + +GM_getTabs(tabsMap => { + GM_log(tabsMap[0]); +}); + +// GM_log + +GM_log('Hello, World!'); +GM_log('Hello, World!', 'Again'); + +// GM_openInTab + +GM_openInTab('http://www.example.com/'); + +GM_openInTab('http://www.example.com/', true); + +const openTabObject = GM_openInTab('http://www.example.com/', { + active: true, + insert: true, + setParent: true +}); + +openTabObject.onclosed = () => { + GM_log('Tab closed', openTabObject.closed); +}; + +openTabObject.close(); + +// GM_notification + +const textNotification: Tampermonkey.NotificationDetails = { + text: 'Notification text', + title: 'Notification title', + image: 'https://tampermonkey.net/favicon.ico', + timeout: 5000, + onclick() { + GM_log(`Notification with id ${this.id} is clicked`); + }, + ondone(clicked) { + GM_log(`Notification with id ${this.id} is clicked ${clicked}`); + } +}; + +const highlightNotification: Tampermonkey.NotificationDetails = { + highlight: true, + onclick: textNotification.onclick, + ondone: textNotification.ondone +}; + +GM_notification(textNotification); +GM_notification(highlightNotification); +GM_notification(textNotification, textNotification.ondone); + +GM_notification( + 'Notification text', + 'Notification title', + 'https://tampermonkey.net/favicon.ico', + function() { + GM_log(`Notification with id ${this.id} is clicked`); + } +); + +// GM_setClipboard + +GM_setClipboard('Some text in clipboard'); +GM_setClipboard('Some text in clipboard', 'text'); +GM_setClipboard('Some text in clipboard', { + type: 'text', + mimetype: 'text/plain' +}); diff --git a/types/tampermonkey/tsconfig.json b/types/tampermonkey/tsconfig.json new file mode 100644 index 0000000000..7ac9df967f --- /dev/null +++ b/types/tampermonkey/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "tampermonkey-tests.ts" + ] +} \ No newline at end of file diff --git a/types/tampermonkey/tslint.json b/types/tampermonkey/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/tampermonkey/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }