Skip to content

Instantly share code, notes, and snippets.

@nolleto
Last active February 4, 2019 17:47
Show Gist options
  • Save nolleto/d0bfc94bf570e7dfa98d1d9b41d51996 to your computer and use it in GitHub Desktop.
Save nolleto/d0bfc94bf570e7dfa98d1d9b41d51996 to your computer and use it in GitHub Desktop.
The purpose of this gist it is to create the easiest way to remove a lot of series or shows without needing to click one by one. This script adds a button the render a modal with your activities grouped.
/**
* How does this script work?
* You open the 'My Activity' (https://www.netflix.com/viewingactivity) page on
* Netflix then open the browser's console and paste the full script and press 'Enter'.
* This will create an 'Erase activities' button beside the title. Clicking on
* the button a dialog will open asking how many times the button 'Show More'
* should be clicked before opening a modal with your activity history in a modal.
* If you put a number, the 'Show More' button will be hit, when the load
* completes it will be hit again. Many times you wish.
* After that, a modal will open with a table with all your series, movies and
* shows loaded in the activity history. So, you can remove the ones you desire.
*
* But why?
* Well, I had a shared Netlfix's account and sometimes when I open my profile
* I see a lot of things I didn't watch because another person just used my profile.
* So, to don't mess up with my timeline I've created this script.
*
*
* Netflix, please make possible add a password on profiles.
*/
const addActivityButton = () => {
const h1 = document.querySelector('h1')
const button = document.createElement('button')
const addCss = addCssAdder(button)
addCss('float', 'right')
addCss('fontSize', '16px')
addCss('margin', '5px 10px 0 0')
addCss('borderRadius', '5px')
button.textContent = 'Erase activities'
button.addEventListener('click', () => {
const loadMore = prompt('Do you want to load more of your activity?\nIf so, how many times do you want we press to "Show More" bottom button?\n(Cancel or leace blank to open the modal withouts load more activity)')
const number = getNumber(loadMore)
loadAndOpenModals(number)
})
h1.append(button)
}
const getNumber = (number) => {
const value = Number(number)
return Number.isNaN(value) ? 0 : value
}
const loadAndOpenModals = (times = 25) => showMore(times, createModalToRemoveHistory)
const clickShowMore = () => {
const button = getShowMoreButton()
button && button.click()
}
const showMore = (times = 10, callback) => {
const counter = times;
const loaderModal = createModal()
const updateLoaderModal = updateModalText(loaderModal)
const update = () => {
const finished = times <= 0
if (finished || noMoreActivitiesToLoad()) {
loaderModal.remove()
callback && callback()
} else {
if (isNotLoading()) {
clickShowMore()
times--;
updateLoaderModal(`Loading more activities: ${counter - times}/${counter}`)
}
setTimeout(update, 50)
}
}
update()
}
const updateModalText = (modal) => (text) => {
const modalContent = modal.querySelector('[data-content]')
modalContent.textContent = text
}
const createModalToRemoveHistory = (showSession = true) => {
const modal = createModal()
const modalContent = modal.querySelector('[data-content]')
const createTableWithData = tableCreator(modalContent)
addFilters(modalContent, createTableWithData)
createTableWithData(getTitlesData(showSession))
}
const tableCreator = (modalContent) => (data) => {
const table = createTable(data)
const currentTable = modalContent.querySelector('table')
currentTable && currentTable.remove()
modalContent.append(table)
}
const addFilters = (modalContent, createTableWithData) => {
const input = createSearchInput()
const checkbox = createCheckboxSession()
const updateTable = tableUpdater(createTableWithData)
input.addEventListener('input', ({ target }) => {
updateTable({ searchText: target.value.trim() })
})
checkbox.addEventListener('change', ({ target }) => {
updateTable({ groupBySession: target.checked })
})
modalContent.append(input)
modalContent.append(checkbox)
}
const tableUpdater = (createTableWithData) => {
let lastOpts = { groupBySession: true }
return (opts) => {
lastOpts = { ...lastOpts, ...opts }
executeOnBackground(() => createTableWithData(createFilteredData(lastOpts)))
}
}
const createFilteredData = (opts) => {
const { searchText, groupBySession } = opts
const data = getTitlesData(groupBySession)
if (searchText) {
const rgx = new RegExp(`(${searchText})`, 'i')
return Object.keys(data)
.filter(str => rgx.test(str))
.reduce((acc, value) => {
acc[addBoldTagInText(value, rgx)] = data[value]
return acc
}, {})
}
return data
}
const createSearchInput = () => {
const input = document.createElement('input')
const addCss = addCssAdder(input)
addCss('width', '500px')
input.placeholder = 'Search for serie, shows or movies...'
return input
}
const createCheckboxSession = () => {
const label = document.createElement('label')
const checkbox = document.createElement('input')
const addCss = addCssAdder(label)
const addCssCheckbox = addCssAdder(checkbox)
addCss('cursor', 'pointer')
addCss('display', 'block')
addCss('margin', '5px 0')
addCssCheckbox('marginRight', '5px')
addCssCheckbox('height', '15px')
checkbox.type = 'checkbox'
checkbox.checked = true
label.textContent = 'Group shows and series by session'
label.prepend(checkbox)
return label
}
const getTitlesData = (showSession) =>
getLis().reduce((acc, li) => addLiReference(acc, getTitleFromLi(li, showSession), li), {})
const measureTime = (key, fn) => {
console.time(key)
const value = fn()
console.timeEnd(key)
return value
}
const addLiReference = (obj, title, li) => {
if (obj[title]) {
obj[title].push(li)
} else {
obj[title] = [li]
}
return obj
}
const getUl = () => document.querySelector('.structural.retable.stdHeight')
const getLis = () => [...getUl().querySelectorAll('.retableRow:not(.retableRemoved)')]
const getTitleFromLi = (li, showSession) => getMainName(li.querySelector('a').textContent, showSession)
const sortByStrings = (a, b) => a.localeCompare(b)
const isNotLoading = () => getUl().nextElementSibling.childElementCount === 0
const scrollToBottom = () => window.scrollTo(0, document.body.scrollHeight)
const isEscapeKey = ({ key }) => key === 'Escape'
const clickRemoveButton = (li) => li.querySelector('.deleteBtn').click()
const getLiLink = (li) => li.querySelector('.title > a').href
const getShowMoreButton = () => document.querySelector('.btn.btn-blue.btn-small')
const noMoreActivitiesToLoad = () => getShowMoreButton().disabled === true
const executeOnBackground = (fn) => setTimeout(fn, 0)
const addBoldTagInText = (str, rgx) => str.replace(rgx, '<b>$1</b>')
const getMainName = (str, showSession) => {
const [title, session] = str.split(':')
return showSession && session ? [title, session].join(':') : title
}
const createModal = () => {
const modal = insertModal()
setupModalLayout(modal)
setupModalBind(modal)
return modal
}
const insertModal = () => {
const body = document.querySelector('body')
body.insertAdjacentHTML('beforeend', modalHTML())
return document.querySelector('#superModal')
}
const setupModalLayout = (modal) => {
const addCss = addCssAdder(modal)
const addCssContent = addCssAdder(modal.querySelector('.modal-content'))
const addCssClose = addCssAdder(modal.querySelector('.close'))
addCss('position', 'fixed')
addCss('zIndex', '1')
addCss('left', '0')
addCss('top', '0')
addCss('width', '100%')
addCss('height', '100%')
addCss('overflow', 'auto')
addCss('backgroundColor', 'rgb(0, 0, 0)')
addCss('backgroundColor', 'rgba(0, 0, 0, 0.4)')
addCssContent('backgroundColor', '#fefefe')
addCssContent('margin', '15% auto')
addCssContent('padding', '20px')
addCssContent('border', '1px solid #888')
addCssContent('width', '60%')
addCssClose('color', '#aaa')
addCssClose('float', 'right')
addCssClose('fontSize', '28px')
addCssClose('fontWeight', 'bold')
addCssClose('cursor', 'pointer')
}
const setupModalBind = (modal) => {
const closeBtn = modal.querySelector('.close')
const keyUpAction = (event) => isEscapeKey(event) && removeModal()
const removeModal = () => {
modal.remove()
document.removeEventListener('keyup', keyUpAction)
}
closeBtn.addEventListener('click', removeModal)
document.addEventListener('keyup', keyUpAction)
}
const addCssAdder = (el) => (prop, value) => {
el.style[prop] = value
}
const modalHTML = () =>
`<div id="superModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div data-content></div>
</div>
</div>`
const createTable = (data) => {
const table = document.createElement('table')
const titles = Object.keys(data).sort(sortByStrings)
table.append(createTableHeader(titles))
titles.map((title, index) => {
const backgroundColor = index % 2 ? 'transparent' : 'lightgray'
table.append(
createTableTr(data, title, backgroundColor)
)
})
addTableStyle(table)
return table
}
const createTableHeader = (titles) => {
const tr = document.createElement('tr')
tr.append(
addTdStyle(createElementWithHTML('th', `${titles.length} titles`))
)
tr.append(
addTdStyle(createElementWithHTML('th'))
)
addTrStyle(tr)
return tr
}
const createTableTr = (data, title, backgroundColor) => {
const lis = data[title]
const tr = document.createElement('tr')
tr.append(
addTdStyle(createTdTitleWithLink(title, lis))
)
tr.append(
addTdStyle(createRemoveButtonTd(lis))
)
addTrStyle(tr, backgroundColor)
return tr
}
const createTdTitleWithLink = (title, lis) => {
const link = getLiLink(lis[0])
const td = createElementWithHTML('td')
const a = createElementWithHTML('a', title)
a.href = link
a.target = '_blank'
td.append(a)
return td
}
const createElementWithHTML = (tag, html) => {
const element = document.createElement(tag)
html && (element.innerHTML = html)
return element
}
const createRemoveButtonTd = (lis) => {
const td = document.createElement('td')
const a = document.createElement('a')
const addCss = addCssAdder(a)
addCss('cursor', 'pointer')
a.textContent = `Remove (${lis.length})`
td.append(a)
bindRemoveButtonTd(a, td, lis)
return td
}
const bindRemoveButtonTd = (a, td, lis) => {
const remover = () => {
lis.map(clickRemoveButton)
a.removeEventListener('click', remover)
a.remove()
td.textContent = `${lis.length} activities removed`
}
a.addEventListener('click', remover)
}
const addTableStyle = (table) => {
const addCss = addCssAdder(table)
addCss('width', '100%')
}
const addTrStyle = (tr, backgroundColor) => {
const addCss = addCssAdder(tr)
addCss('border', '1px solid black')
backgroundColor && addCss('backgroundColor', backgroundColor)
}
const addTdStyle = (td) => {
const addCss = addCssAdder(td)
addCss('padding', '5px')
return td
}
addActivityButton()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment