Skip to content

Instantly share code, notes, and snippets.

@baptx
Last active August 2, 2024 06:04
Show Gist options
  • Save baptx/1e61eef2e1ec200b6e7b32409f79c07f to your computer and use it in GitHub Desktop.
Save baptx/1e61eef2e1ec200b6e7b32409f79c07f to your computer and use it in GitHub Desktop.
Instagram API: view and backup direct messages from a web browser
/*
Instagram API: view and backup direct messages from a web browser
Since April 2020, Instagram has a web version to send and read direct messages so my Instagram scripts are not longer needed and I would not recommend using them unless you really need it, to avoid being banned
(never happened to me with Instagram but WhatsApp is owned by Facebook also and they did it to users registering from an unofficial app like yowsup: https://github.com/tgalal/yowsup/commit/88b8ad9581fa22dac330ee3a05fec4e485dfa634#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5)
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