- Install Tampermonkey
- View "Raw" to install the script
Last active
March 20, 2022 23:26
-
-
Save alankyshum/4dbe38f9caabfc7d3784ad9fb012ef8f to your computer and use it in GitHub Desktop.
My own custom tampermonkey scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Wait for the element to appear in the DOM within specified timeout | |
* @param {string} selector | |
* @param {number} timeout | |
* @returns {Promise<HTMLElement[]>} | |
*/ | |
async function getElements(selector = "", timeout = 20000) { | |
let intervalClearer; | |
let timeRemaining = timeout; | |
return new Promise((resolve, reject) => { | |
intervalClearer = setInterval(() => { | |
const queriedElements = document.querySelectorAll(selector); | |
const isElementsLoaded = /[A-Z]/.test(queriedElements[0]?.textContent); | |
if (timeRemaining < 0) { | |
clearInterval(intervalClearer); | |
console.error(new Error(`${selector} was not found`)); | |
return; | |
} else if (isElementsLoaded) { | |
clearInterval(intervalClearer); | |
resolve(queriedElements); | |
return; | |
} | |
timeRemaining -= 500; | |
}, 500); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Finviz - Screener to map | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description Render a button linking to a maps view of currently viewing stocks from screener page | |
// @author Alan Shum | |
// @match *://elite.finviz.com/screener.ashx* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=finviz.com | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
"use strict"; | |
class FinvizExtended { | |
/** @type {HTMLStyleElement} */ | |
styleElement; | |
/** @type {HTMLDivElement} */ | |
wrapperElement; | |
constructor() { | |
this.wrapperElement = document.createElement('div'); | |
this.wrapperElement.id = "ky-script--wrapper"; | |
this.styleElement = document.createElement('style'); | |
this.styleElement.textContent = ` | |
#ky-script--wrapper { | |
top: 16px; | |
right: 8px; | |
position: fixed; | |
z-index: 99999; | |
} | |
.ky-script--extension-button { | |
background: var(--button-color); | |
color: white; | |
box-shadow: 0px 0px 10px var(--button-color); | |
opacity: 0.7; | |
padding: 4px 16px; | |
border: 2px solid white; | |
border-radius: 4px; | |
margin: 16px; | |
transition: opacity .3s; | |
} | |
.ky-script--extension-button:hover, | |
.ky-script--extension-button:focus { | |
opacity: 1; | |
} | |
`; | |
this.wrapperElement.appendChild(this.styleElement); | |
} | |
injectScreenerButton() { | |
this._injectStyle` | |
.ky-script--screener-button { | |
--button-color: black; | |
} | |
`; | |
const allTickers = Array.from(document.querySelectorAll('a[href^="quote.ashx"]')) | |
.map((a) => new URL(a.href).searchParams.get('t')); | |
const tickers = Array.from(new Set(allTickers)).join(','); | |
const screenerUrl = `https://elite.finviz.com/bubbles.ashx?x=PE&y=operMargin&size=relativeVolume&color=sector&idx=any&tickers=${tickers}`; | |
const screenerAnchor = document.createElement('a'); | |
screenerAnchor.setAttribute('href', screenerUrl); | |
screenerAnchor.setAttribute('target', '_blank'); | |
screenerAnchor.textContent = `View in map ➾`; | |
screenerAnchor.classList.add('ky-script--screener-button'); | |
screenerAnchor.classList.add('ky-script--extension-button'); | |
this.wrapperElement.appendChild(screenerAnchor); | |
} | |
_injectStyle(cssStr) { | |
this.styleElement.textContent += cssStr; | |
} | |
} | |
/* ========================================================================== */ | |
/* MAIN */ | |
/* ========================================================================== */ | |
const finvizExtended = new FinvizExtended(); | |
finvizExtended.injectScreenerButton(); | |
document.body.appendChild(finvizExtended.wrapperElement); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Robinhood - Portfolio in Finviz | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description View portfolio in Finviz | |
// @author Alan Shum | |
// @match https://robinhood.com/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=robinhood.com | |
// @grant none | |
// ==/UserScript== | |
(async function () { | |
"use strict"; | |
/** | |
* Main class for all the extended features for Finviz | |
*/ | |
class FinvizExtended { | |
/** | |
* Containing all features overlaying the Robinhood webpage | |
*/ | |
overflowWrapper = new OverflowWrapper(); | |
/** | |
* Inject Finviz chart underneath the Robinhood chart | |
*/ | |
async injectFinvizChart() { | |
const [parentContainer] = await getElements('#sdp-price-chart-graph'); | |
if (!parentContainer) return; | |
const currentTicker = location.pathname.match(/\/stocks\/(?<ticker>[A-Z]+)/i)?.groups?.ticker; | |
if (!currentTicker) return; | |
const yearlyChartImage = document.createElement('img'); | |
yearlyChartImage.src = `https://charts2.finviz.com/chart.ashx?ty=c&s=l&p=d&ta=1&t=${currentTicker}`; | |
yearlyChartImage.style.width = '100%'; | |
const linkWrapper = document.createElement('a'); | |
linkWrapper.setAttribute('target', '_blank'); | |
linkWrapper.href = `https://elite.finviz.com/quote.ashx?t=${currentTicker}`; | |
linkWrapper.appendChild(yearlyChartImage); | |
parentContainer.appendChild(linkWrapper); | |
} | |
async injectViewChartsButton() { | |
await this.overflowWrapper.appendStyle` | |
.ky-script--screener-button { | |
--button-color: black; | |
} | |
`; | |
const targetTickers = await getElements('[data-testid="PositionCell"]'); | |
const allTickers = Array.from(targetTickers) | |
.map(pos => pos.textContent.match(/[A-Z]+/)[0]); | |
const tickers = Array.from(new Set(allTickers)).join(','); | |
const screenerUrl = `https://elite.finviz.com/screener.ashx?v=211&t=${tickers}&o=-relativevolume`; | |
const screenerAnchor = document.createElement('a'); | |
screenerAnchor.setAttribute('href', screenerUrl); | |
screenerAnchor.setAttribute('target', '_blank'); | |
screenerAnchor.textContent = "View Finviz Charts ➾"; | |
screenerAnchor.classList.add('ky-script--screener-button'); | |
screenerAnchor.classList.add('ky-script--extension-button'); | |
this.overflowWrapper.appendChild(screenerAnchor); | |
} | |
} | |
/* ========================================================================== */ | |
/* BUTTONS OVERLAYING THE WEBPAGE */ | |
/* ========================================================================== */ | |
/** | |
* Elements overlay the whole webpage will be inserted into this wrapper. | |
*/ | |
class OverflowWrapper { | |
/** @type {HTMLDivElement} */ | |
wrapperElement; | |
WRAPPER_ID = 'ky-script--wrapper'; | |
get styleElement() { | |
return this.wrapperElement?.querySelector('style'); | |
} | |
/** | |
* Append an element to the overflow wrapper, and init wrapper if not present | |
* @param {HTMLElement} child | |
*/ | |
async appendChild(child) { | |
if (!this.wrapperElement) { | |
await this.initOverflowWrapper(); | |
} | |
this.wrapperElement.appendChild(child); | |
} | |
/** | |
* Create the wrapper element and append it to the body | |
*/ | |
async initOverflowWrapper() { | |
this.wrapperElement = document.createElement('div'); | |
this.wrapperElement.id = this.WRAPPER_ID; | |
const styleElement = document.createElement('style'); | |
styleElement.textContent = ` | |
#${this.WRAPPER_ID} { | |
top: 64px; | |
right: 8px; | |
position: fixed; | |
z-index: 99999; | |
} | |
.ky-script--extension-button { | |
background: var(--button-color); | |
color: white; | |
box-shadow: 0px 0px 10px var(--button-color); | |
opacity: 0.7; | |
padding: 4px 16px; | |
border: 2px solid white; | |
border-radius: 4px; | |
margin: 16px; | |
text-decoration: none; | |
transition: opacity .3s; | |
} | |
.ky-script--extension-button:hover, | |
.ky-script--extension-button:focus { | |
opacity: 1; | |
} | |
`; | |
this.wrapperElement.appendChild(styleElement); | |
document.body.appendChild(this.wrapperElement); | |
} | |
/** | |
* Append styles class into the style element inside the overflow wrapper | |
* @param {string} cssString | |
*/ | |
async appendStyle(cssString) { | |
if (!this.wrapperElement) { | |
await this.initOverflowWrapper(); | |
} | |
this.styleElement.textContent += cssString; | |
} | |
} | |
/* ========================================================================== */ | |
/* UTILITY METHODS */ | |
/* ========================================================================== */ | |
/** | |
* Wait for the element to appear in the DOM within specified timeout | |
* @param {string} selector | |
* @param {number} timeout | |
* @returns {HTMLElement[]} | |
*/ | |
async function getElements(selector = '', timeout = 20000) { | |
let intervalClearer; | |
let timeRemaining = timeout; | |
return new Promise((resolve, reject) => { | |
intervalClearer = setInterval(() => { | |
const queriedElements = document.querySelectorAll(selector); | |
const isElementsLoaded = /[A-Z]/.test(queriedElements[0]?.textContent); | |
if (timeRemaining < 0) { | |
clearInterval(intervalClearer); | |
console.error(new Error(`${selector} was not found`)); | |
return; | |
} else if (isElementsLoaded) { | |
clearInterval(intervalClearer); | |
resolve(queriedElements); | |
return; | |
} | |
timeRemaining -= 500; | |
}, 500); | |
}); | |
} | |
/* ========================================================================== */ | |
/* MAIN */ | |
/* ========================================================================== */ | |
const finvizExtended = new FinvizExtended(); | |
finvizExtended.injectViewChartsButton(); | |
finvizExtended.injectFinvizChart(); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name name-of-the-script | |
// @namespace http://tampermonkey.net/ | |
// @description Adds 2 custom search filters to limit search results to the past 6 months or 2 years. | |
// @contributor Alan Shum | |
// @version 0.1 | |
// @icon http://www.google.com/favicon.ico | |
// @include /(http|https)?://.*\.google\.[^\/]+?/(#.*|search\?.*)?$/ | |
// @grant none | |
// ==/UserScript== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Ticktick - Matrix as homepage | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description Show the Ticktick Eisenhower Matrix as homepage | |
// @author Alan Shum | |
// @match https://ticktick.com/webapp/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=ticktick.com | |
// @grant none | |
// ==/UserScript== | |
(async function () { | |
"use strict"; | |
class ClockWidget { | |
clock = (() => { | |
const spanElement = document.createElement("span"); | |
spanElement.style.marginLeft = "4px"; | |
return spanElement; | |
})(); | |
_isClockInjected = false; | |
get _currentTimeStamp() { | |
const now = new Date(); | |
const hours = String(now.getHours()).padStart(2, "0"); | |
const minutes = String(now.getMinutes()).padStart(2, "0"); | |
const seconds = String(now.getSeconds()).padStart(2, "0"); | |
return `${hours}:${minutes}:${seconds}`; | |
} | |
get _workFocusRemaining() { | |
return this._remainingFocusTime([9, 0], [17, 0]); | |
} | |
get _personalFocusRemaining() { | |
return this._remainingFocusTime([17, 0], [22, 30]); | |
} | |
get _formattedContent() { | |
return ` | |
${this._currentTimeStamp} | 🤖: ${this._workFocusRemaining} | 🤪: ${this._personalFocusRemaining} | |
`; | |
} | |
async initClockWidget() { | |
this.clockContainer = (await getElements("#container-main h1"))[0]; | |
this.clockContainer.style.display = "flex"; | |
if (!this.clockContainer) return; | |
this.clockContainer.appendChild(this.clock); | |
this._isClockInjected = true; | |
} | |
renderClock() { | |
if (!this._isClockInjected) return; | |
this.clock.innerHTML = this._formattedContent; | |
return setInterval(this.renderClock.bind(this), 1000); | |
} | |
_formatTimestamp(timeInMs) { | |
const timeInS = timeInMs / 1000; | |
const hours = Math.floor(timeInS / 3600); | |
const minutes = Math.floor((timeInS % 3600) / 60); | |
const seconds = Math.floor((timeInS % 3600) % 60); | |
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart( | |
2, | |
"0" | |
)}:${String(seconds).padStart(2, "0")}`; | |
} | |
_remainingFocusTime(focusStartHours, focusEndHours) { | |
const now = new Date(); | |
const focusTimeStart = new Date().setHours(...focusStartHours); | |
const focusTimeEnd = new Date().setHours(...focusEndHours); | |
if (now < focusTimeStart) return "-"; | |
return this._formatTimestamp(focusTimeEnd - now); | |
} | |
} | |
/** | |
* Wait for the element to appear in the DOM within specified timeout | |
* @param {string} selector | |
* @param {number} timeout | |
* @returns {Promise<HTMLElement[]>} | |
*/ | |
async function getElements(selector = "", timeout = 20000) { | |
let intervalClearer; | |
let timeRemaining = timeout; | |
return new Promise((resolve, reject) => { | |
intervalClearer = setInterval(() => { | |
const queriedElements = document.querySelectorAll(selector); | |
const isElementsLoaded = /[A-Z]/.test(queriedElements[0]?.textContent); | |
if (timeRemaining < 0) { | |
clearInterval(intervalClearer); | |
console.error(new Error(`${selector} was not found`)); | |
return; | |
} else if (isElementsLoaded) { | |
clearInterval(intervalClearer); | |
resolve(queriedElements); | |
return; | |
} | |
timeRemaining -= 500; | |
}, 500); | |
}); | |
} | |
const clockWidget = new ClockWidget(); | |
await clockWidget.initClockWidget(); | |
clockWidget.renderClock(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment