InsCNM

Automatically fetch media information from Instagram URLs.

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

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