InsCNM

Automatically fetch media information from Instagram URLs, with Toastify notifications.

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

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