Instagram API: view and backup direct messages from a web browser
/* | |
Instagram API: view and backup direct messages from a web browser | |
1) Log in on Instagram web version and go to your profile page | |
(the home page will not work because it loads data when scrolling down and the direct messages will be displayed at the bottom of the page) | |
2) Modify HTTP headers with a browser addon like Header Editor (https://addons.mozilla.org/en-US/firefox/addon/header-editor/) | |
Content-Security-Policy (response header on domain www.instagram.com): original data with https://i.instagram.com/ added to connect-src | |
report-uri https://www.instagram.com/security/csp_report/; default-src 'self' https://www.instagram.com; img-src https: data: blob:; font-src https: data:; media-src 'self' blob: https://www.instagram.com https://*.cdninstagram.com https://*.fbcdn.net; manifest-src 'self' https://www.instagram.com; script-src 'self' https://instagram.com https://www.instagram.com https://*.www.instagram.com https://*.cdninstagram.com wss://www.instagram.com https://*.facebook.com https://*.fbcdn.net https://*.facebook.net 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' https://*.www.instagram.com https://www.instagram.com 'unsafe-inline'; connect-src 'self' https://instagram.com https://www.instagram.com https://i.instagram.com https://*.www.instagram.com https://graph.instagram.com https://*.graph.instagram.com https://*.cdninstagram.com https://api.instagram.com wss://www.instagram.com wss://edge-chat.instagram.com https://*.facebook.com https://*.fbcdn.net https://*.facebook.net chrome-extension://boadgeojelhgndaghljhdicfkmllpafd blob:; worker-src 'self' blob: https://www.instagram.com; frame-src 'self' https://instagram.com https://www.instagram.com https://staticxx.facebook.com https://www.facebook.com https://web.facebook.com https://connect.facebook.net https://m.facebook.com; object-src 'none'; upgrade-insecure-requests | |
User-Agent (request header on domain i.instagram.com) | |
Instagram 85.0.0.21.100 Android (23/6.0.1; 538dpi; 1440x2560; LGE; LG-E425f; vee3e; en_US) | |
3) Run the following JavaScript code in the web console of your browser or in a tool like Firefox scratchpad. | |
The function InstagramDMthreads should be used first, then you have to comment the call to it and uncoment the call to InstagramDMmessages, | |
which takes a thread ID as parameter (found with the previous function InstagramDMthreads). | |
A textarea will appear to the bottom of the page so you can copy-paste to backup data (if you don't see it, you have to close the web console or scroll down). | |
If you want to send direct messages without using Android / iOS and the official proprietary app, you can use open source desktop apps like IG:dm, do cURL requests to the API or use my web browser script instagram-api_send_message.js. | |
*/ | |
/* Usage */ | |
//InstagramDMthreads(); | |
//InstagramDMmessages("XXX"); // parameter needs to be a string since it is too big for an integer | |
/* CONFIG */ | |
var backup = true; // if backup is set to false, only the last threads and messages are displayed, in browser console instead of textarea (number can be configured in limit variable) | |
var limit = 100; // 20 threads and messages by default, per API request | |
/* END CONFIG */ | |
var start; | |
var data; | |
var usernames; | |
// backup will return: thread ID, date + time + timezone, userid, username, last message | |
function InstagramDMthreads() | |
{ | |
initVariables(); // clear variables to allow reusing functions several time | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", "https://i.instagram.com/api/v1/direct_v2/inbox/" + "?limit=" + limit); | |
xhr.withCredentials = true; | |
xhr.addEventListener("load", function() { | |
var response = JSON.parse(xhr.responseText); | |
if (backup) { | |
scrollInbox(response); | |
} | |
else { | |
parseInbox(response); | |
console.log(data); | |
} | |
}); | |
xhr.send(); | |
} | |
function parseInbox(response) | |
{ | |
var length = response.inbox.threads.length; | |
for (var i = 0; i < length; ++i) { | |
// length is 0 for conversations with yourself | |
var userid = response.inbox.threads[i].users.length != 0 ? response.inbox.threads[i].users[0].pk : response.viewer.pk; | |
var username = response.inbox.threads[i].users.length != 0 ? response.inbox.threads[i].users[0].username : response.viewer.username; | |
data[start + i] = '"""' + response.inbox.threads[i].thread_id + '"""\t' | |
+ new Date(response.inbox.threads[i].last_activity_at / 1000).toString() + '\t' | |
+ userid + '\t' | |
+ username + '\t' | |
+ '"' + getMessageText(response.inbox.threads[i].items[0]).replace(/"/g, '""') + '"'; | |
// allow multiline CSV string with tab separator and escape double quote string delimiter | |
} | |
start += length; | |
} | |
function scrollInbox(response) | |
{ | |
parseInbox(response); | |
var cursor = response.inbox.oldest_cursor; | |
if (cursor) { | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", "https://i.instagram.com/api/v1/direct_v2/inbox/?cursor=" + cursor + "&limit=" + limit); | |
xhr.withCredentials = true; | |
xhr.addEventListener("load", function() { | |
var response = JSON.parse(xhr.responseText); | |
scrollInbox(response); | |
}); | |
xhr.send(); | |
} | |
else { | |
displayData(); | |
} | |
} | |
// backup will return: date + time + timezone, username, message | |
function InstagramDMmessages(thread) | |
{ | |
initVariables(); // clear variables to allow reusing functions several time | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", "https://i.instagram.com/api/v1/direct_v2/threads/" + thread + "/" + "?limit=" + limit); | |
xhr.withCredentials = true; | |
xhr.addEventListener("load", function() { | |
var response = JSON.parse(xhr.responseText); | |
var xhr2 = new XMLHttpRequest(); | |
xhr2.open("GET", "https://i.instagram.com/api/v1/users/" + response.thread.viewer_id + "/info/"); | |
xhr2.withCredentials = true; | |
xhr2.addEventListener("load", function() { | |
var response2 = JSON.parse(xhr2.responseText); | |
usernames[response.thread.viewer_id] = response2.user.username; | |
usernames[response.thread.users[0].pk] = response.thread.users[0].username; | |
if (backup) { | |
scrollThread(thread, response); | |
} | |
else { | |
parseThread(thread, response); | |
console.log(data); | |
} | |
}); | |
xhr2.send(); | |
}); | |
xhr.send(); | |
} | |
function parseThread(thread, response) | |
{ | |
var length = response.thread.items.length; | |
for (var i = 0; i < length; ++i) { | |
data[start + i] = new Date(response.thread.items[i].timestamp / 1000).toString() + '\t' | |
+ usernames[response.thread.items[i].user_id] + '\t' | |
+ '"' + getMessageText(response.thread.items[i]).replace(/"/g, '""') + '"'; | |
} | |
start += length; | |
} | |
function scrollThread(thread, response) | |
{ | |
parseThread(thread, response); | |
if (response.thread.prev_cursor != "MINCURSOR") { | |
var cursor = response.thread.oldest_cursor; | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", "https://i.instagram.com/api/v1/direct_v2/threads/" + thread + "/?cursor=" + cursor + "&limit=" + limit); | |
xhr.withCredentials = true; | |
xhr.addEventListener("load", function() { | |
var response = JSON.parse(xhr.responseText); | |
scrollThread(thread, response); | |
}); | |
xhr.send(); | |
} | |
else { | |
displayData(); | |
} | |
} | |
function getMessageText(item) | |
{ | |
var type = item.item_type; | |
var text; | |
switch (type) { | |
case "text": | |
text = item.text; | |
break; | |
case "link": // used if the message contains a link | |
text = item.link.text; | |
break; | |
case "action_log": // used when someone likes a message | |
text = item.action_log.description; | |
break; | |
case "like": // used when someone sends a like | |
text = item.like; | |
break; | |
case "media": | |
if (item.media.media_type == 1) { | |
text = item.media.image_versions2.candidates[0].url; | |
} | |
else { // media_type is 1 for images and 2 for videos | |
text = item.media.video_versions[0].url; | |
} | |
break; | |
case "animated_media": // for GIF files | |
text = item.animated_media.images.fixed_height.url; | |
break; | |
case "voice_media": // for audio recordings | |
text = item.voice_media.media.audio.audio_src; | |
break; | |
} | |
return text; | |
} | |
function initVariables() | |
{ | |
start = 0; | |
data = []; | |
usernames = []; | |
} | |
function displayData() | |
{ | |
var box = document.createElement("textarea"); | |
box.value = data.join("\n"); | |
document.body.appendChild(box); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment