Skip to content

Instantly share code, notes, and snippets.

@sbolel
Created June 17, 2017 02:13
Show Gist options
  • Save sbolel/0385db4cfd3ba5b67a912bb21a6cfc62 to your computer and use it in GitHub Desktop.
Save sbolel/0385db4cfd3ba5b67a912bb21a6cfc62 to your computer and use it in GitHub Desktop.
Instagram auto-liker in the browser using Chrome Dev Tools
/**
* @desc Injects jQuery into Instagram feed page and likes X posts at a time
*
* Usage: Open https://www.instagram.com/ in Chrome and login to view your feed;
* run this script in the JS console to like posts in your feed in batches.
*
* @param {number} start - starting post index (should be 0 unless manually continuing a prev. exec.)
* @param {number} count - number of posts to like per batch (like X posts, wait a little, repeat)
* @param {number} interval - number of milliseconds to wait between batches, +/- some randomness
*/
(function(start, count, interval) {
// @todo find like button without using hard-coded class selector.
// note: "._tk4ba" was the button class in my feed when I tried this out.
const selector = '._tk4ba'
const _body = () => document.getElementsByTagName('body')[0]
const _getButtons = () => $('._tk4ba').toArray()
const _plusOrMinus = () => Math.random() < 0.5 ? -1 : 1
const _randomTimeout = () => (_plusOrMinus() * Math.floor((Math.random() * 500))) + 1500
const _scrollToBottom = () => _body().scrollTop = _body().scrollHeight
function _inject(doc, cb) {
return (opts => {
return Array.isArray(opts)
? Promise.all(opts.map(item => this.loadScript(item, opts)))
: new Promise((resolve, reject) => {
let r = false
const t = doc.getElementsByTagName('script')[0]
const s = doc.createElement('script')
if (typeof opts === 'object' && typeof opts.src !== 'undefined') {
for (key in opts) s[key] = opts[key]
} else if (typeof opts === 'string') {
s.src = opts
} else {
throw new Error('Script src undefined')
}
s.onerror = s.onabort = reject
s.onload = s.onreadystatechange = () => {
if (!r && (!this.readyState || this.readyState == 'complete')) {
r = true
resolve(s.src)
}
}
t.parentNode.insertBefore(s, t)
})
})({
async: true,
src: 'https://code.jquery.com/jquery-3.2.1.min.js',
type: 'text/javascript'
})
.then(src => {
console.log('Injected', src)
_body.scrollTop = 0;
})
}
function _like(els) {
if (!els || typeof els === 'undefined' || els.length < 1) {
console.debug('Ran out of posts. Scrolling to bottom...')
_scrollToBottom()
setTimeout(() => {}, 750)
}
return Promise.all(els.map(el => new Promise((resolve, reject) => {
if (el.firstChild.textContent === 'Like') {
setTimeout(() => {
el.click()
console.debug(`CLICKED -> ${el}`)
return resolve(el)
}, _randomTimeout())
} else {
console.debug(`Skipped -> ${el}`)
return resolve(el)
}
})))
.then(res => console.debug(`Resolved ${res}`))
.catch(err => console.error(err))
}
_inject(document)
.then(() => {
if (typeof interval === 'number') {
let idx = start
const getInterval = () => interval + _randomTimeout()
const setNewTimeout = () => setTimeout(() => {
console.debug(`Starting over at ${idx}!`)
const elsArr = $(selector).toArray()
_like(elsArr.slice(idx, idx+count))
idx += count
setNewTimeout()
}, getInterval())
setNewTimeout()
} else {
_like($(selector).toArray().slice(start, count))
}
})
/// sample usage with ~4 second delay between batches of 3 posts
})(0, 3, 4000)
/**
* @desc Injects jQuery into Instagram hashtags page and likes 1 post at a time
*
* Usage: Open https://www.instagram.com/explore/tags/javascript/ in Chrome;
* run this script in the JS console to like top posts one-at-a-time.
* @param {number} start - starting post index (should be 0 unless manually continuing a prev. exec.)
* @param {number} interval - number of milliseconds to wait between batches, +/- some randomness
*/
(function(start, interval) {
const count = 1 // override count, we can only view 1 post at a time for tags.
const selector = 'a' // this works since we know the structure of the page.
const _body = () => document.getElementsByTagName('body')[0]
const _getButtons = () => $(selector).toArray()
const _plusOrMinus = () => Math.random() < 0.5 ? -1 : 1
const _randomTimeout = () => (_plusOrMinus() * Math.floor((Math.random() * 500))) + 1500
const _randomTimeoutShort = () => (_plusOrMinus() * Math.floor((Math.random() * 200))) + 1000
const _scrollToBottom = () => _body().scrollTop = _body().scrollHeight
function _inject(doc, cb) {
return (opts => {
return Array.isArray(opts)
? Promise.all(opts.map(item => this.loadScript(item, opts)))
: new Promise((resolve, reject) => {
let r = false
const t = doc.getElementsByTagName('script')[0]
const s = doc.createElement('script')
if (typeof opts === 'object' && typeof opts.src !== 'undefined') {
for (key in opts) s[key] = opts[key]
} else if (typeof opts === 'string') {
s.src = opts
} else {
throw new Error('Script src undefined')
}
s.onerror = s.onabort = reject
s.onload = s.onreadystatechange = () => {
if (!r && (!this.readyState || this.readyState == 'complete')) {
r = true
resolve(s.src)
}
}
t.parentNode.insertBefore(s, t)
})
})({
async: true,
src: 'https://code.jquery.com/jquery-3.2.1.min.js',
type: 'text/javascript'
})
.then(src => {
console.log('Injected', src)
_body.scrollTop = 0;
})
}
function _like(els) {
var commonClass
if (!els || typeof els === 'undefined' || els.length < 1) {
console.log('Ran out of posts...')
// @todo click the "Load more posts" button
return
}
return Promise.all(els.map(el => new Promise((resolve, reject) => {
el.click()
setTimeout(() => {
var e = $('article')[1]
var btn = e.children[2].firstChild.firstChild.firstChild
if (btn.classList.contains('coreSpriteLikeHeartOpen')) {
setTimeout(() => {
btn.click()
console.log('CLICKED ->', btn)
}, _randomTimeout())
} else {
console.log('Skipped ->', btn)
}
setTimeout(() => {
$('button').last()[0].click()
return resolve(e)
}, _randomTimeoutShort())
}, _randomTimeout())
})))
.then(res => console.log(`Resolved ${res}`))
.catch(err => console.error(err))
}
function _doLike() {
_inject(document)
.then(() => {
if (typeof interval === 'number') {
let idx = start
const getInterval = () => interval + _randomTimeout()
const setNewTimeout = () => setTimeout(() => {
console.log(`Starting over at ${idx}!`)
const elsArr = $(selector).toArray()
commonClass = $(selector)[0].classList[0]
_like(elsArr.slice(idx, idx+count))
idx += count
setNewTimeout()
}, getInterval())
setNewTimeout()
} else {
_like($(selector).toArray().slice(start, count))
}
})
}
_doLike()
/// sample usage with ~4 second delay between batches of 3 posts
})(0, 3000)
@DaycareJr
Copy link

[Report Only] Refused to load the script 'https://code.jquery.com/jquery-3.2.1.min.js' because it violates the following Content Security Policy directive: "script-src *.facebook.com *.fbcdn.net *.facebook.net 'unsafe-inline' 'unsafe-eval' blob: data: 'self' *.teststagram.com *.instagram.com static.cdninstagram.com *.google-analytics.com *.google.com". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

async @ VM200:42
(anonymous) @ VM200:24
_inject @ VM200:44
_doLike @ VM200:88
(anonymous) @ VM200:108
(anonymous) @ VM200:110
VM200:42 Refused to load the script 'https://code.jquery.com/jquery-3.2.1.min.js' because it violates the following Content Security Policy directive: "script-src *.facebook.com *.fbcdn.net .facebook.net 127.0.0.1: 'unsafe-inline' blob: data: 'self' *.teststagram.com *.instagram.com static.cdninstagram.com *.google-analytics.com *.google.com 'wasm-unsafe-eval'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

async @ VM200:42
(anonymous) @ VM200:24
_inject @ VM200:44
_doLike @ VM200:88
(anonymous) @ VM200:108
(anonymous) @ VM200:110
undefined
_7InplydoCg.js?_nc_x=Ij3Wp8lg5Kz:59 ErrorUtils caught an error:

UnhandledRejection: {"isTrusted":true}

Subsequent non-fatal errors won't be logged; see https://fburl.com/debugjs.
errorListener @ _7InplydoCg.js?_nc_x=Ij3Wp8lg5Kz:59
reportNormalizedError @ _7InplydoCg.js?_nc_x=Ij3Wp8lg5Kz:59
reportError @ _7InplydoCg.js?_nc_x=Ij3Wp8lg5Kz:59
va @ _7InplydoCg.js?_nc_x=Ij3Wp8lg5Kz:59

@sbolel
Copy link
Author

sbolel commented Jun 28, 2023

@DaycareJr yeah, it looks like they fixed the issue that made this hack possible using Content Security Policy. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src. I wrote this 6 years ago when they hadn't included CSP directives in their html yet. There's no way to make this script work now that it's in place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment