InsCNM

Automatically fetch media information from Instagram URLs.

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

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