Created
June 17, 2017 02:13
-
-
Save sbolel/0385db4cfd3ba5b67a912bb21a6cfc62 to your computer and use it in GitHub Desktop.
Instagram auto-liker in the browser using Chrome Dev Tools
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
/** | |
* @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) |
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
/** | |
* @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 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
[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