Action
Save Instagram Posts
Posted by garbonsai,
Last update
9 months ago
UPDATES
9 months ago
Bugfix for posts that have been removed (soft 404s).
9 months ago
Bugfix for posts that have been removed (soft 404s).
9 months ago
Added support for reels.
9 months ago
Change keyboard shortcut to avoid conflict.
9 months ago
This is a complete rewrite due to Meta turning off the JSON endpoint for Instagram posts. Unfortunately, comments can no longer be retrieved and I’m still working on TV and reels support.
over 2 years ago
- added option to change draft title prefix
- added option to remove tags from newly created draft(s)
- added option to delete source draft on success
- made error handling more robust
- switched to built in error, info, success, and warning messages
- cleaned up and refactored code
Searches draft for any Instagram post or reel URLs and saves the contents of each as a new draft. Does not work for Private Accounts. Edit the template tags to modify the templates and settings.
Steps
-
defineTemplateTag
name imageTemplate
template 
-
defineTemplateTag
name videoTemplate
template -
defineTemplateTag
name postTemplate
template [[draftTitle]] [[postCaption]] [[postMedia]] [[[postUser]]](https://www.instagram.com/[[postUser]]), [[[postDate]]]([[postUrl]])
-
defineTemplateTag
name titlePrefix
template #
-
defineTemplateTag
name captionPrefix
template -
defineTemplateTag
name mediaGlue
template -
defineTemplateTag
name dateFormat
template %a, %d %b %Y %T %z
-
defineTemplateTag
name escapeHashes
template false
-
defineTemplateTag
name reuseTitle
template false
-
defineTemplateTag
name copyTags
template true
-
defineTemplateTag
name tagsToAdd
template @instagram
-
defineTemplateTag
name tagsToRemove
template instagram_old
-
defineTemplateTag
name archiveNewDrafts
template true
-
defineTemplateTag
name flagNewDrafts
template true
-
defineTemplateTag
name loadNewDrafts
template true
-
script
// ******************** changelog ******************** // // v3.0.3: bug fix for posts that have been removed // v3.0.2: added support for reels // v3.0.1: changed keyboard shortcut // v3.0: // - complete rewrite because meta turned off JSON endpoint - thanks meta :( // - now uses the JSON-LD embedded in each post's HTML on Instagram // - now uses template tags for settings and templates // - now honors action's 'After Success' setting // - remove ability to retrieve comments - thanks meta :( // v2.0.2: fix for 'Script Error: ReferenceError: Can't find variable: requestUrl' // v2.0.1: added option to delete source draft on warning and error // v2.0: // - added option to change draft title prefix // - added option to remove tags from newly created draft(s) // - added option to delete source draft on success // - made error handling more robust // - switched to built in error, info, success, and warning messages // - cleaned up and refactored code // v1.9.5: added support to archive newly created draft // v1.9.4: jump to top of draft if loaded // v1.9.3: code cleanup // v1.9.2: added support to flag newly created draft // v1.9.1: added support for reels and tv // v1.9: tweaked default template // v1.8: updated to allow for running against multiple drafts at once // 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 ******************** // // default video template const defVideoTemplate = '<video controls height="[[postVideoHeight]]" width="[[postVideoWidth]]" ' + 'style="height:auto;max-width:[[postVideoWidth]]px;width:100%"><source ' + 'src="[[postVideoUrl]]" type="video/mp4"></video>'; // default post template const defPostTemplate = `[[draftTitle]] [[postCaption]] [[postMedia]] [[[postUser]]](https://www.instagram.com/[[postUser]]), [[[postDate]]]([[postUrl]])`; // ******************** functions ******************** // // alert the user and handle breaking errors function alertUser(type, message, log) { switch (type) { case 'error': app.displayErrorMessage(message); throw log; break; case 'success': app.displaySuccessMessage(message); break; default: app.displayInfoMessage(message); } } // convert 'true' and 'false' to boolean or return a default function strToBool(string, otherwise = false) { return string === 'true' || string === 'false' ? JSON.parse(string) : otherwise; } // check if a template tag exists and if not, return a default function ttExists(tag, otherwise) { return tag !== '' ? tag : otherwise; } // verify a string is valid JSON function verifyJson(string) { let result = JSON.parse(string); if (!result || typeof result !== 'object') { alertUser( 'error', 'JSON-LD is not valid.', `Invalid JSON-LD: ${string}` ); } return result; } // ******************** main ******************** // // set various options using template tags or defaults const imageTemplate = ttExists( draft.getTemplateTag('imageTemplate'), '' ); const videoTemplate = ttExists( draft.getTemplateTag('videoTemplate'), defVideoTemplate ); const postTemplate = ttExists( draft.getTemplateTag('postTemplate'), defPostTemplate ); const titlePrefix = ttExists(draft.getTemplateTag('titlePrefix'), '# '); const captionPrefix = draft.getTemplateTag('captionPrefix'); const mediaGlue = draft.getTemplateTag('mediaGlue'); const dateFormat = ttExists( draft.getTemplateTag('dateFormat'), '%a, %d %b %Y %T %z' ); const escapeHashes = strToBool(draft.getTemplateTag('escapeHashes')); const reuseTitle = strToBool(draft.getTemplateTag('reuseTitle')); const copyTags = strToBool(draft.getTemplateTag('copyTags', true)); const tagsToAdd = draft.getTemplateTag('tagsToAdd').split(','); const tagsToRemove = draft.getTemplateTag('tagsToRemove').split(','); const archiveNewDrafts = strToBool(draft.getTemplateTag('archiveNewDrafts')); const flagNewDrafts = strToBool(draft.getTemplateTag('flagNewDrafts')); const loadNewDrafts = strToBool(draft.getTemplateTag('loadNewDrafts', true)); // find any Instagram post URLs in the draft try { let postUrls = []; let postUrlRegExp = new RegExp( /(?:https?:\/\/)?(?:w{3}\.)?instagram\.com\/(p|reel)\/[a-zA-Z0-9_\-]+/, 'gi' ); let postUrlMatch; while ((postUrlMatch = postUrlRegExp.exec(draft.content)) !== null) { postUrls.push([postUrlMatch[0], postUrlMatch[1]]); } if (postUrls.length === 0) { alertUser('error', 'No post URLs found.', 'No post URLs found.'); } // retrieve each post URL that was found postUrls.forEach(function (postUrl) { let http = HTTP.create(); let httpRequest = http.request({ method: 'GET', url: postUrl[0], }); if (!httpRequest.success) { alertUser( 'error', 'Unable to retrieve post.', `Unable to retrieve ${postUrl[0]}.` ); } // extract the JSON-LD post data let jsonRegExp = new RegExp( /<script type="application\/ld\+json" nonce=".*?">(.*?)<\/script>/, 'i' ); let jsonData = jsonRegExp.exec(httpRequest.responseText); if (jsonData === null) { alertUser( 'error', 'No JSON-LD found.', `No JSON-LD found at ${postUrl[0]}.` ); } let postData = verifyJson(jsonData[1]); // format the post media let postMedia = []; if (postUrl[1] === 'p') { postData.image.forEach(function (postImage) { draft.setTemplateTag('postImage', postImage.url); postMedia.push(draft.processTemplate(imageTemplate)); }); } else if (postUrl[1] === 'reel') { postData.video.forEach(function (postVideo) { draft.setTemplateTag('postVideoUrl', postVideo.contentUrl); draft.setTemplateTag('postVideoHeight', postVideo.height); draft.setTemplateTag('postVideoWidth', postVideo.width); postMedia.push(draft.processTemplate(videoTemplate)); }); } // format the post caption let postCaption = postData.articleBody; if (escapeHashes) postCaption = postCaption.replace(/^#/gm, '\\#'); postCaption = postCaption.replace(/^/gm, captionPrefix); // format the post date let postDate = strftime(new Date(postData.dateCreated), dateFormat); // set the necessary template tags draft.setTemplateTag('postUrl', postUrl[0]); draft.setTemplateTag('postMedia', postMedia.join(mediaGlue)); draft.setTemplateTag('postCaption', postCaption); draft.setTemplateTag('postUser', postData.author.identifier.value); draft.setTemplateTag('postDate', postDate); reuseTitle ? draft.setTemplateTag('draftTitle', draft.title) : draft.setTemplateTag( 'draftTitle', titlePrefix + postData.identifier.value ); // create the new draft let d = Draft.create(); d.content = draft.processTemplate(postTemplate); if (copyTags) draft.tags.forEach((tag) => d.addTag(tag)); tagsToAdd.forEach((tag) => d.addTag(tag)); tagsToRemove.forEach((tag) => d.removeTag(tag)); d.isFlagged = flagNewDrafts; d.isArchived = archiveNewDrafts; d.update(); // load the new draft if (loadNewDrafts) { editor.load(d); editor.setSelectedRange(0, 0); } }); // display a success notification alertUser('success', 'Post successfully saved.', ''); // log thrown errors } catch (e) { console.log(e); }
Options
-
After Success Trash Notification Error Log Level Error
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.