InsCNM

Automatically fetch media information from Instagram URLs.

当前为 2024-10-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name InsCNM
  3. // @namespace http://tampermonkey.net/
  4. // @version 5.1
  5. // @description Automatically fetch media information from Instagram URLs.
  6. // @author Belugu
  7. // @match https://www.instagram.com/p/*
  8. // @match https://www.instagram.com/reel/*
  9. // @grant GM_xmlhttpRequest
  10. // @require https://code.jquery.com/jquery-3.6.0.min.js
  11. // @icon https://iili.io/29TPCR1.jpg
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. // Main logic
  16. $(document).ready(async function() {
  17. if (window.location.href.match(/^https:\/\/www\.instagram\.com\/(p|reel)\/[a-zA-Z0-9_-]+\/$/)) {
  18. const shortcode = extractShortcodeFromURL(window.location.href);
  19. if (shortcode) {
  20. const mediaInfo = await fetchMediaInfoByShortcode(shortcode);
  21. if (mediaInfo) {
  22. const { taken_at, comment_count, like_count } = mediaInfo.items[0];
  23. showMediaInfoUI(`
  24. <p>发布时间: ${new Date(taken_at * 1000).toLocaleString()} <button class="copy-btn" data-copy="${new Date(taken_at * 1000).toLocaleString()}" style="background:none; border:none; cursor:pointer;">🗿</button></p>
  25. <p>评论数: ${comment_count} <button class="copy-btn" data-copy="${comment_count}" style="background:none; border:none; cursor:pointer;">🗿</button></p>
  26. <p>点赞数: ${like_count} <button class="copy-btn" data-copy="${like_count}" style="background:none; border:none; cursor:pointer;">🗿</button></p>
  27. `);
  28. } else {
  29. showMediaInfoUI('<p>获取媒体信息失败。</p>');
  30. }
  31. }
  32. }
  33. });
  34.  
  35. function showMediaInfoUI(mediaInfoHtml) {
  36. const container = $(
  37. `<div id="instagram-fetcher-ui" style="position:fixed; top:20px; right:-350px; background:white; color:black; border:1px solid #ccc; padding:15px; z-index:10000; width:300px; border-radius:10px;">
  38. <div style="display: flex; justify-content: space-between; align-items: center;">
  39. <h3 style="color:black; margin:0;">输入网址后按 Enter 搜索</h3>
  40. <button id="close-ui-button" style="background:none; border:none; font-size:20px; cursor:pointer; color:black;">&times;</button>
  41. </div>
  42. <div id="input-area" style="display: flex; align-items: center; margin-top:10px;">
  43. <input type="text" id="instagram-url-input" placeholder="输入网址" style="width:80%; padding:5px; color:black; border:1px solid #ccc; border-radius:5px; box-shadow:none; outline:none;"/>
  44. </div>
  45. <div id="media-info-output" style="margin-top:15px; font-size:14px;">${mediaInfoHtml}</div>
  46. </div>`
  47. );
  48.  
  49. $('body').append(container);
  50. $('#instagram-fetcher-ui').animate({ right: '20px' }, 400);
  51.  
  52. $('#instagram-url-input').on('keyup', async function(e) {
  53. if (e.which === 13) { // Enter key pressed
  54. const url = $('#instagram-url-input').val();
  55. console.log("User entered URL:", url);
  56. if (url) {
  57. const shortcode = extractShortcodeFromURL(url);
  58. if (shortcode) {
  59. $('#media-info-output').text('正在获取媒体信息...');
  60. const mediaInfo = await fetchMediaInfoByShortcode(shortcode);
  61. if (mediaInfo) {
  62. const { taken_at, comment_count, like_count } = mediaInfo.items[0];
  63. $('#media-info-output').html(
  64. `<p>发布时间: ${new Date(taken_at * 1000).toLocaleString()} <button class="copy-btn" data-copy="${new Date(taken_at * 1000).toLocaleString()}" style="background:none; border:none; cursor:pointer;">🗿</button></p>
  65. <p>评论数: ${comment_count} <button class="copy-btn" data-copy="${comment_count}" style="background:none; border:none; cursor:pointer;">🗿</button></p>
  66. <p>点赞数: ${like_count} <button class="copy-btn" data-copy="${like_count}" style="background:none; border:none; cursor:pointer;">🗿</button></p>`
  67. );
  68. } else {
  69. $('#media-info-output').text('获取媒体信息失败。');
  70. }
  71. } else {
  72. $('#media-info-output').text('无效的 Instagram URL。');
  73. }
  74. }
  75. }
  76. });
  77.  
  78. $('#close-ui-button').click(function() {
  79. $(this).css({ transform: 'scale(0.95)' });
  80. setTimeout(() => $(this).css({ transform: 'scale(1)' }), 100);
  81. $('#instagram-fetcher-ui').animate({ right: '-350px' }, 400, function() {
  82. $(this).remove();
  83. });
  84. });
  85.  
  86. $(document).on('click', '.copy-btn', function() {
  87. const textToCopy = $(this).data('copy');
  88. navigator.clipboard.writeText(textToCopy).then(() => {
  89. showNotification("已复制到剪贴板");
  90. }).catch(err => {
  91. console.error('复制失败:', err);
  92. });
  93. });
  94. }
  95.  
  96. // Function to get a random gradient color
  97. function getRandomGradient() {
  98. const gradients = [
  99. '#6a11cb, #2575fc', // Blue gradient
  100. '#11998e, #38ef7d', // Green gradient
  101. '#ff7e5f, #feb47b', // Original orange gradient
  102. '#ff6a00, #ee0979', // Red gradient
  103. '#43cea2, #185a9d' // Aqua gradient
  104. ];
  105. return gradients[Math.floor(Math.random() * gradients.length)];
  106. }
  107.  
  108. // Function to show a simple notification with animation
  109. function showNotification(message) {
  110. const gradient = getRandomGradient(); // Get the gradient before usage
  111. const notification = $(
  112. `<div class="notification" style="position:fixed; bottom:-60px; right:20px; background: linear-gradient(to right, ${gradient}); color:white; padding:10px; border-radius:5px; z-index:10001; opacity:0.9;">
  113. ${message}
  114. </div>`
  115. );
  116.  
  117. // Adjust the position of existing notifications
  118. $('.notification').each(function() {
  119. const currentBottom = parseInt($(this).css('bottom'));
  120. $(this).css('bottom', currentBottom + 60 + 'px');
  121. });
  122.  
  123. $('body').append(notification);
  124. notification.animate({ bottom: '40px', opacity: 1 }, 400).delay(3000).animate({ opacity: 0 }, 400, function() {
  125. $(this).remove();
  126. });
  127. }
  128.  
  129. // Listen for Alt+N to toggle the input UI
  130. document.addEventListener('keydown', function (e) {
  131. if (e.altKey && e.key === 'n') {
  132. const container = $('#instagram-fetcher-ui');
  133. if (container.length) {
  134. container.find('#close-ui-button').click();
  135. } else {
  136. showMediaInfoUI('<p>输入网址后按 Enter 搜索。</p>');
  137. }
  138. }
  139. });
  140.  
  141. // Function to extract the shortcode from a given URL
  142. function extractShortcodeFromURL(url) {
  143. try {
  144. const urlObj = new URL(url);
  145. const pathSegments = urlObj.pathname.split('/');
  146. console.log("URL Object:", urlObj);
  147. console.log("Path Segments:", pathSegments);
  148. if (pathSegments[1] === 'p' || pathSegments[1] === 'reel') {
  149. return pathSegments[2] ? pathSegments[2] : null;
  150. }
  151. return null;
  152. } catch (error) {
  153. console.error("Error extracting shortcode from URL:", error);
  154. return null;
  155. }
  156. }
  157.  
  158. // Function to fetch media info by shortcode
  159. async function fetchMediaInfoByShortcode(shortcode) {
  160. const mediaId = await getMediaID(shortcode);
  161. if (!mediaId) {
  162. console.error("Failed to fetch media ID.");
  163. $('#media-info-output').text('获取媒体 ID 失败,请稍后重试。');
  164. return null;
  165. }
  166.  
  167. try {
  168. const mediaInfo = await getMediaInfo(mediaId);
  169. console.log("Media Info:", mediaInfo);
  170. return mediaInfo;
  171. } catch (error) {
  172. console.error("Error retrieving media info:", error);
  173. return null;
  174. }
  175. }
  176.  
  177. // Function to fetch media ID using shortcode
  178. async function getMediaID(shortcode) {
  179. try {
  180. const response = await fetch(`https://www.instagram.com/p/${shortcode}/`, {
  181. headers: {
  182. "User-Agent": window.navigator.userAgent,
  183. "Accept": "text/html"
  184. }
  185. });
  186. if (!response.ok) {
  187. throw new Error(`HTTP error! status: ${response.status}`);
  188. }
  189. const html = await response.text();
  190. const mediaIdMatch = html.match(/"media_id":"(\d+)"/);
  191. if (mediaIdMatch) {
  192. return mediaIdMatch[1];
  193. } else {
  194. console.error("Media ID not found in page HTML.");
  195. }
  196. } catch (error) {
  197. console.error("Error fetching media ID:", error);
  198. }
  199. return null;
  200. }
  201.  
  202. // Function to get app ID
  203. function getAppID() {
  204. let result = null;
  205. $('script[type="application/json"]').each(function() {
  206. const regexp = /"APP_ID":"([0-9]+)"/ig;
  207. const matcher = $(this).text().match(regexp);
  208. if (matcher != null && result == null) {
  209. result = [...$(this).text().matchAll(regexp)];
  210. }
  211. });
  212. return (result) ? result.at(0).at(-1) : null;
  213. }
  214.  
  215. // Function to get media info using media ID
  216. async function getMediaInfo(mediaId) {
  217. return new Promise((resolve, reject) => {
  218. let getURL = `https://i.instagram.com/api/v1/media/${mediaId}/info/`;
  219.  
  220. if (mediaId == null) {
  221. console.error("Cannot call Media API because the media ID is invalid.");
  222. reject("Cannot call Media API because the media ID is invalid.");
  223. return;
  224. }
  225.  
  226. GM_xmlhttpRequest({
  227. method: "GET",
  228. url: getURL,
  229. headers: {
  230. "User-Agent": window.navigator.userAgent,
  231. "Accept": "application/json",
  232. 'X-IG-App-ID': getAppID()
  233. },
  234. onload: function (response) {
  235. try {
  236. if (response.finalUrl == getURL) {
  237. let obj = JSON.parse(response.responseText);
  238. resolve(obj);
  239. } else {
  240. let finalURL = new URL(response.finalUrl);
  241. if (finalURL.pathname.startsWith('/accounts/login')) {
  242. console.error("The account must be logged in to access Media API.");
  243. } else {
  244. console.error('Unable to retrieve content because the API was redirected to "' + response.finalUrl + '"');
  245. }
  246. reject(-1);
  247. }
  248. } catch (error) {
  249. console.error("Error parsing JSON response:", error);
  250. reject(error);
  251. }
  252. },
  253. onerror: function (err) {
  254. reject(err);
  255. }
  256. });
  257. });
  258. }
  259. })();