Instagram Source Opener

Allows the user to open the original source of an instagram post or story. No jQuery

目前为 2018-10-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         Instagram Source Opener
// @version      0.6
// @description  Allows the user to open the original source of an instagram post or story. No jQuery
// @author       jomifepe
// @icon         https://www.instagram.com/favicon.ico
// @require      https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
// @include      https://www.instagram.com/*
// @grant        GM_addStyle
// @namespace 	 https://gf.qytechs.cn/users/192987
// ==/UserScript==

(function() {
	"use strict";

	/* this script relies a lot on class names, I'll keep an eye on changes */
	const C_STORY_CONTAINER = "yS4wN";
	const C_STORY_MEDIA_CONTAINER = "qbCDp";
	const C_POST_IMG = "FFVAD";
	const C_POST_VIDEO = "tWeCl";
	const C_SINGLE_POST_CONTAINER = "JyscU";
	const C_MULTI_POST_SCROLLER = "MreMs";
	const C_MULTI_POST_LIST_ITEM = "_-1_m6";
	const S_POSTS_CONTAINER = ".cGcGK > div > div";
	const S_POST_BUTTONS = ".eo2As > section";

	const C_BTN_STORY = "iso-story-btn";
	const C_BTN_STORY_CONTAINER = "iso-story-container";
	const C_POST_WITH_BUTTON = "iso-post";
	const C_BTN_POST_OUTER_SPAN = "iso-post-container";
	const C_BTN_POST = "iso-post-btn";
	const C_BTN_POST_INNER_SPAN = "iso-post-span";

	/* injects the needed CSS into DOM */
	injectStyles();

	/* triggered whenever a new instagram post is loaded on the feed */
	document.arrive(`${S_POSTS_CONTAINER} article`, (node) => {
		generatePostButton(node);
	});

	/* triggered whenever a single post is opened (on a profile) */
	document.arrive(`.${C_SINGLE_POST_CONTAINER}`, (node) => {
		generatePostButton(node);
	});

	/* triggered whenever a story is opened */
	document.arrive(`.${C_STORY_CONTAINER}`, (node) => {
		generateStoryButton(node);
	});

	window.onload = ((e) => {
		/* aditional check because sometimes the arive functions aren't triggered when the page is hard reloaded */
		if (window.location.href.indexOf("instagram.com/p/") > -1) {
			let node = document.querySelector(`.${C_SINGLE_POST_CONTAINER}`);
			if (node != null) {
				generatePostButton(node);
			}
		} else if (window.location.href.indexOf("/stories/") > -1) {
			let node = document.querySelector(`.${C_STORY_CONTAINER}`);
			if (node == null) {
				generateStoryButton(node);
			}
		}
	})

	function generateStoryButton(node) {
		/* exits if the story button already exists */
		if (elementExistsInNode(`.${C_BTN_STORY_CONTAINER}`, node)) return;

		try {
			let buttonStoryContainer = document.createElement("span");
			let buttonStory = document.createElement("button");

			buttonStoryContainer.classList.add(C_BTN_STORY_CONTAINER);
			buttonStory.classList.add(C_BTN_STORY);
			buttonStoryContainer.setAttribute("title", "Open source");
			buttonStory.addEventListener("click", () => openStoryContent(node));
			
			buttonStoryContainer.appendChild(buttonStory);
			node.appendChild(buttonStoryContainer);
		} catch (err) {
			showDefaultErrorMessage(err);
		}
	}

	function generatePostButton(node) {
		/* exits if the post button already exists */
		if (elementExistsInNode(`.${C_BTN_POST_OUTER_SPAN}`, node)) return;

		try {
			let buttonsContainer = node.querySelector(S_POST_BUTTONS);
			let newElementOuterSpan = document.createElement("span");
			let newElementButton = document.createElement("button");
			let newElementInnerSpan = document.createElement("span");

			newElementOuterSpan.classList.add(C_BTN_POST_OUTER_SPAN);
			newElementButton.classList.add(C_BTN_POST);
			newElementInnerSpan.classList.add(C_BTN_POST_INNER_SPAN);
			newElementOuterSpan.setAttribute("title", "Open source");
			newElementButton.addEventListener("click", () => openPostSourceFromSrcAttribute(node));

			newElementButton.appendChild(newElementInnerSpan);
			newElementOuterSpan.appendChild(newElementButton);
			buttonsContainer.appendChild(newElementOuterSpan);
			node.classList.add(C_POST_WITH_BUTTON);
		} catch (err) {
			showDefaultErrorMessage(err);
		}
	}

	function openStoryContent(node) {
		try {
			let container = node.querySelector(`.${C_STORY_MEDIA_CONTAINER}`);
			let video = container.querySelector("video");
			let image = container.querySelector("img");
			if (video) {
				let videoElement = video.querySelector("source");
				let videoSource = videoElement ? videoElement.getAttribute("src") : null;
				if (!videoSource) {
					throw "Failed to open video source";
				}
				window.open(videoSource, "_blank");
			} else if (image) {
				let imageSource = image.getAttribute("src");
				if (!imageSource) {
					throw "Failed to open image source";
				}
				window.open(imageSource, "_blank");
			} else {
				throw "Failed to open media source"
			}
		} catch (err) {
			showDefaultErrorMessage(err);
		}
	}

	function openPostSourceFromSrcAttribute(node) {
		let nodeListItems = node.querySelectorAll(`.${C_MULTI_POST_LIST_ITEM}`);

		try {
			if (/* is multi post */ nodeListItems.length != 0) {
				let scroller = node.querySelector(`.${C_MULTI_POST_SCROLLER}`);
				let scrollerOffset = Math.abs((() => {
					let scrollerStyles = window.getComputedStyle(scroller);
					return parseInt(scrollerStyles.getPropertyValue("transform").split(",")[4]);
				})());

				let mediaIndex = 0;
				if (scrollerOffset != 0) {
					let totalWidth = 0;
					nodeListItems.forEach(item => {
						let itemStyles = window.getComputedStyle(item);
						totalWidth += parseInt(itemStyles.getPropertyValue("width"));
					});
					mediaIndex = ((scrollerOffset * nodeListItems.length) / totalWidth);
				}

				openPostMediaSource(nodeListItems[mediaIndex]);
			} else /* is single post */ {
				openPostMediaSource(node);
			}
		} catch (err) {
			showDefaultErrorMessage(err);
		}
		
	}

	function openPostMediaSource(nodeToSearchForMedia) {
		let image = nodeToSearchForMedia.querySelector(`.${C_POST_IMG}`);
		let video = nodeToSearchForMedia.querySelector(`.${C_POST_VIDEO}`);
		if (!image && !video) {
			throw "Failed to open source, no media found";
		}
		window.open((video || image).getAttribute("src"), "_blank");
	}

	function elementExistsInNode(selector, node) {
		return (node.querySelector(selector) != null);
	}

	function injectStyles() {
		let b64icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfiAxwDOBTFNFQBAAABKklEQVQ4y6WTvUoDQRSFvwkbxCqijY2okEIEixQpBMHKykIFC8H/yiew8Rl8i4ClCoJYWGkhaGXjrkmTbsUiTQoVf45Ndp1lZ8eIp7vnnnPvnBkG/gjjb2uAOcoW0fplnvaVRccAaIFZhnPqW3OkMa4Zz84o60RunAFoQm2bDDhgmSsOHad7BjBtrXFjb3jUi0Y8KUYV2hvQly77kH/qKTFIF33Id5MsHoMl30njdwoNlnw75SqaLDC45EnLYbDkW/lZOYMl3wRQTTW/4bQn3+jVoUK/YUqxPrSe1pGin26QD2wizVM15+7LDlykadIseswSbwzhgUpUeLWJO0nTHsOSpIa1XSsc06VR8PnqrGKom3t7xp66KkasxUw+AA0y4/iiADEP5p3/4BuEXi9gkPrfQgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wMy0yOFQwMzo1NjoyMCswMjowMO7sj9MAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDMtMjhUMDM6NTY6MjArMDI6MDCfsTdvAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAABJRU5ErkJggg==";
		let styles = [
			`.${C_BTN_POST_OUTER_SPAN}{margin-left:10px;margin-right:-10px;}`,
			`.${C_BTN_POST}{outline:none;-webkit-box-align:center;align-items:center;background:00;border:0;cursor:pointer;display:flex;-webkit-box-flex:0;flex-grow:0;-webkit-box-pack:center;justify-content:center;min-height:40px;min-width:40px;padding:0;}`,
			`.${C_BTN_POST_INNER_SPAN}{display:block;background-repeat:no-repeat;background-position:100%-26px;height:24px;width:24px;background-image:url(/static/bundles/base/sprite_glyphs.png/4b550af4600d.png);cursor:pointer;}`,
			`.${C_BTN_STORY}{border:none;position:fixed;top:0;right:0;margin:20px;cursor:pointer;width:24px;height:24px;background-color:transparent;background-image:url(${b64icon})}`
		];

		styles.forEach((style) => GM_addStyle(style));
	}

	function showDefaultErrorMessage(error) {
		alert(`${error}\n\nSorry for the inconvenience, the developer is on it!`);
	}
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址