Action

Save Instagram Posts

Posted by Garbonsai, Last update about 1 month ago

Searchs the current selection or Draft for any Instagram post URLs (instagram.com/p/) and saves the contents of each post as a new Draft. Does not work for Private Accounts. Edit the script to modify the templates and settings.

Steps

  • script

    // ******************** changelog ******************** //
    
    // v1.7: added ability to reuse title of current draft for new draft(s)
    // v1.6: new default template to better accommodate searching Drafts
    // v1.5: gracefully handle non-fatal errors
    // v1.4: added ability to optionally both tag and load newly created drafts
    // v1.3: fix for changes to how paged threaded comments are queried
    // v1.2: code cleanup and refactoring
    // v1.1: now gets all comments, not just most recent
    // v1.0: initial release
    
    // ******************** templates ******************** //
    
    // image template
    var imageTpl = `![]([[postImage]])`;
    
    // comment template
    var commentTpl = `[[commentText]] —[[[commentUser]]](https://www.instagram.com/[[commentUser]]), [[commentDate]]`;
    
    // threaded comment template
    var threadTpl = `[[commentText]] —[[[commentUser]]](https://www.instagram.com/[[commentUser]]), [[commentDate]]`;
    
    // post template
    var postTpl = `# [[draftTitle]]
    [[postCaption]]
    
    [[postImages]]
    
    [[[postUser]]](https://www.instagram.com/[[postUser]]), [[[postDate]]]([[postUrl]])
    
    ## Comments
    [[postComments]]`;
    
    // ******************** settings ******************** //
    
    // strftime format string for post and comment dates
    var dateFmt = '%a, %d %b %Y %T %z';
    
    // prefix for linebreaks found in the caption
    var captionPrefix = '';
    
    // prefix for linebreaks found in comments
    var commentPrefix = '> ';
    
    // prefix for linebreaks found in threaded comments
    var threadPrefix = '> > ';
    
    // string used to join multiple images together
    var imageGlue = '';
    
    // string used to join multiple comments together
    var commentGlue = '\n\n';
    
    // escape hashes found at the beginning of a line in the caption and comments
    var escHashes = true;
    
    // copy any tags from the draft being processed to the new draft(s)
    var copyTags = true;
    
    // other tags to add to the new draft(s)
    var addTags = [ 'instagram' ];
    
    // load new draft(s) in the editor
    var loadDraft = true;
    
    // if it begins with '# ', reuse the title of current draft for the new draft(s)
    var reuseTitle = true;
    
    // ******************** helpers ******************** //
    
    // find and return any instagram post URLs
    function findPostUrls( haystack ) {
    	var needle = new RegExp(
    		/(?:https?:\/\/)?(?:w{3}\.)?instagram\.com\/p\/[a-zA-Z0-9_\-]+/,
    		'gi'
    	);
    	var matches = [];
    	while ( ( match = needle.exec( haystack ) ) !== null ) {
    		matches.push( match[0] );
    	}
    	if ( matches.length === 0 ) {
    		throw( 'No Instagram post URLs were found.' );
    	} else {
    		return matches;
    	}
    }
    
    // request a URL and return the response
    function getUrl( url ) {
    	var http = HTTP.create();
    	return http.request({
    		'method': 'GET',
    		'url': url
    	});
    }
    
    // helper function for sorting comments by date
    function compareDates( a, b ) {
    	if ( a.node.created_at < b.node.created_at ){
    		return -1;
    	} else if ( a.node.created_at > b.node.created_at ){
    		return 1;
    	} else {
    		return 0;
    	}
    }
    
    // log something and alert the user
    function alertUser( message ) {
    	console.log( message );
    //	alert( message );
    }
    
    // get and return all comments, sorted by date
    function getComments( recentComments, vars, queryHash, type ) {
    	var comments = recentComments.edges;
    	var hasNextPage = recentComments.page_info.has_next_page;
    	vars.after = recentComments.page_info.end_cursor;
    	vars.first = 50;
    	while ( hasNextPage ) {
    		var encVars = encodeURIComponent( JSON.stringify( vars ) );
    		var requestUrl = `https://www.instagram.com/graphql/query/?query_hash=${queryHash}&variables=${encVars}`;
    		var request = getUrl( requestUrl );
    		if ( request.success ) {
    			var response = JSON.parse( request.responseText );
    			var olderComments = type === 'thread' ?
    				response.data.comment.edge_threaded_comments :
    				response.data.shortcode_media.edge_media_to_parent_comment;
    			comments.push.apply( comments, olderComments.edges )
    			hasNextPage = olderComments.page_info.has_next_page;
    			vars.after = olderComments.page_info.end_cursor;
    		} else {
    			alertUser( `${request.statusCode}: ${request.error} for ${requestUrl}` );
    		}
    	}
    	return comments.sort( compareDates );
    }
    
    // format and return a comment block
    function formatComment( comment, prefix, tpl ) {
    	var text = comment.node.text;
    	if ( escHashes ) text = text.replace( /^#/gm, '\\#' );
    	text = text.replace( /^/gm, prefix );
    	var date = strftime( comment.node.created_at * 1000, dateFmt );
    	draft.setTemplateTag( 'commentText', text );
    	draft.setTemplateTag( 'commentDate', date );
    	draft.setTemplateTag( 'commentUser', comment.node.owner.username );
    	return draft.processTemplate( tpl );
    }
    
    // ******************** main ******************** //
    
    // handle any errors
    try {
    
    	// find any instagram post URLs in the current selection or draft
    	var selection = editor.getSelectedText();
    	var postUrls = selection.length !== 0 ?
    		findPostUrls( selection ) :
    		findPostUrls( editor.getText() );
    
    	// check if the title of the current draft can and should be reused
    	if ( reuseTitle && draft.title.substr( 0, 2 ) === '# ' ) {
    		reuseTitle = draft.title.substr( 2 );
    	} else {
    		reuseTitle = false;
    	}
    
    	// get the JSON data for each URL that was found
    	postUrls.forEach( function( postUrl ) {
    		var request = getUrl( `${postUrl}/?__a=1` );
    		if ( request.success ) {
    			var response = JSON.parse( request.responseText );
    			var postData = response.graphql.shortcode_media;
    
    			// parse and format the images
    			var postImages = [];
    			if ( postData.hasOwnProperty( 'edge_sidecar_to_children' ) ) {
    				postData.edge_sidecar_to_children.edges.forEach( function( postImage ) {
    					draft.setTemplateTag( 'postImage', postImage.node.display_url );
    					postImages.push( draft.processTemplate( imageTpl ) );
    				});
    			} else {
    				draft.setTemplateTag( 'postImage', postData.display_url );
    				postImages.push( draft.processTemplate( imageTpl ) );
    			}
    
    			// parse and format the caption
    			var postCaption = postData.edge_media_to_caption.edges[0].node.text;
    			if ( escHashes ) postCaption = postCaption.replace( /^#/gm, '\\#' );
    			postCaption  = postCaption.replace( /^/gm, captionPrefix );
    
    			// parse and format the comments
    			var postComments = [];
    			var comments = getComments(
    				postData.edge_media_to_parent_comment,
    				{ shortcode: postData.shortcode },
    				'97b41c52301f77ce508f55e66d17620e',
    				'comment'
    			);
    			comments.forEach( function( comment ) {
    				postComments.push( formatComment( comment, commentPrefix, commentTpl ) );
    				var threads = getComments(
    					comment.node.edge_threaded_comments,
    					{ comment_id: comment.node.id },
    					'51fdd02b67508306ad4484ff574a0b62',
    					'thread'
    				);
    				threads.forEach( function( thread ) {
    					postComments.push( formatComment( thread, threadPrefix, threadTpl ) );
    				});
    			});
    
    			// set the necessary template tags
    			draft.setTemplateTag( 'postUrl', postUrl );
    			draft.setTemplateTag( 'postImages', postImages.join( imageGlue ) );
    			draft.setTemplateTag( 'postCaption', postCaption );
    			draft.setTemplateTag( 'postComments', postComments.join( commentGlue ) );
    			draft.setTemplateTag( 'postUser', postData.owner.username );
    			draft.setTemplateTag( 'postDate', strftime( postData.taken_at_timestamp * 1000, dateFmt ) );
    			reuseTitle ?
    				draft.setTemplateTag( 'draftTitle', reuseTitle ) :
    				draft.setTemplateTag( 'draftTitle', postData.shortcode );
    
    			// create the new draft
    			var d = Draft.create();
    			d.content = draft.processTemplate( postTpl );
    			if ( copyTags ) draft.tags.forEach( tag => d.addTag( tag ) );
    			addTags.forEach( tag => d.addTag( tag ) );
    			d.update();
    			editor.load( d );
    		} else {
    			alertUser( `${request.statusCode}: ${request.error} for ${postUrl}` );
    		}
    	});
    
    // log and display any errors
    } catch( error ) {
    	console.log( error );
    	alert( error );
    }
    

Options

  • After Success Trash
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.