Flickr Always Visible Stats

Makes like and comment counts always visible on Flickr photo thumbnails in profile pages

  1. // ==UserScript==
  2. // @name Flickr Always Visible Stats
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Makes like and comment counts always visible on Flickr photo thumbnails in profile pages
  6. // @author fapek GPT
  7. // @match https://*.flickr.com/people/*
  8. // @match https://*.flickr.com/photos/*
  9. // @match *.flickr.com/photos/*
  10.  
  11. // @license MIT
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Function to create and inject custom CSS
  19. function injectCustomCSS() {
  20. const css = `
  21. /* Force interaction bar to be always visible */
  22. .photo-list-photo-view .interaction-bar,
  23. .overlay-target .interaction-bar {
  24. display: none !important; /* Hide the original interaction bar completely */
  25. opacity: 0 !important;
  26. visibility: hidden !important;
  27. }
  28.  
  29. /* Style for our custom stats bar */
  30. .always-visible-stats {
  31. display: flex !important;
  32. opacity: 1 !important;
  33. visibility: visible !important;
  34. position: absolute !important;
  35. bottom: 0 !important;
  36. left: 0 !important;
  37. right: 0 !important;
  38. background: rgb(0, 0, 0) !important; /* Fully opaque background */
  39. padding: 5px !important;
  40. transition: background 0.2s ease !important;
  41. z-index: 99 !important; /* Higher z-index to ensure it's on top */
  42. pointer-events: auto !important;
  43. }
  44.  
  45. /* Hide the title and attribution to save space */
  46. .photo-list-photo-view .interaction-bar .text {
  47. display: none;
  48. }
  49.  
  50. /* Make the engagement section take full width */
  51. .photo-list-photo-view .interaction-bar .engagement {
  52. display: flex;
  53. width: 100%;
  54. justify-content: space-around;
  55. }
  56.  
  57. /* Style for engagement items */
  58. .photo-list-photo-view .interaction-bar .engagement-item {
  59. display: flex;
  60. align-items: center;
  61. color: white;
  62. padding: 2px 5px;
  63. font-weight: bold;
  64. }
  65.  
  66. /* Hover effect on the bar */
  67. .photo-list-photo-view:hover .interaction-bar {
  68. background: rgba(0, 0, 0, 0.8);
  69. }
  70.  
  71. /* Make engagement count more visible */
  72. .photo-list-photo-view .interaction-bar .engagement-count {
  73. margin-left: 3px;
  74. font-size: 14px;
  75. text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
  76. }
  77.  
  78. /* Special styling for favorite counts */
  79. .photo-list-photo-view .interaction-bar .fave .engagement-count {
  80. color: #fffc00;
  81. }
  82.  
  83. /* Special styling for comment counts */
  84. .photo-list-photo-view .interaction-bar .comment .engagement-count {
  85. color: #00ffff;
  86. }
  87.  
  88. /* Hide the curate button to save space */
  89. .photo-list-photo-view .interaction-bar .curate {
  90. display: none;
  91. }
  92.  
  93. /* Ensure icons are visible */
  94. .photo-list-photo-view .interaction-bar svg,
  95. .photo-list-photo-view .interaction-bar i {
  96. filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.8));
  97. }
  98. `;
  99.  
  100. const styleElement = document.createElement('style');
  101. styleElement.textContent = css;
  102. document.head.appendChild(styleElement);
  103. }
  104.  
  105. // Function to parse like counts including K format (1K, 1.5K etc.)
  106. function parseCount(text) {
  107. text = text.trim();
  108.  
  109. // Handle K format (e.g., "1K", "1.3K")
  110. if (text.endsWith('K')) {
  111. const numPart = parseFloat(text.replace('K', ''));
  112. return Math.round(numPart * 1000);
  113. }
  114.  
  115. return parseInt(text);
  116. }
  117.  
  118. // Function to stylize like counts with the same levels as your other script
  119. function stylizeLikeCounts() {
  120. // Find all engagement count elements for favorites
  121. const likeElements = document.querySelectorAll('.fave .engagement-count');
  122.  
  123. likeElements.forEach(function(likeElement) {
  124. const originalText = likeElement.innerText.trim();
  125. const likeCount = parseCount(originalText);
  126.  
  127. if (!isNaN(likeCount)) {
  128. // Reset existing custom styles
  129. likeElement.style.fontSize = '';
  130. likeElement.style.textShadow = '';
  131.  
  132. // Style based on number of likes
  133. if (likeCount >= 1 && likeCount <= 5) {
  134. likeElement.style.fontSize = '14px';
  135. } else if (likeCount >= 6 && likeCount <= 15) {
  136. likeElement.style.fontSize = '16px';
  137. likeElement.style.textShadow = '0 0 3px #fffc00';
  138. } else if (likeCount >= 16 && likeCount <= 30) {
  139. likeElement.style.fontSize = '18px';
  140. likeElement.style.textShadow = '0 0 4px #fffc00';
  141. } else if (likeCount >= 31 && likeCount <= 60) {
  142. likeElement.style.fontSize = '20px';
  143. likeElement.style.textShadow = '0 0 5px #fffc00';
  144. } else if (likeCount >= 61 && likeCount <= 100) {
  145. likeElement.style.fontSize = '22px';
  146. likeElement.style.textShadow = '0 0 6px #fffc00';
  147. } else if (likeCount >= 101 && likeCount <= 500) {
  148. likeElement.style.fontSize = '24px';
  149. likeElement.style.textShadow = '0 0 8px #fffc00, 0 0 12px #fffc00';
  150. } else if (likeCount >= 501 && likeCount <= 1000) {
  151. likeElement.style.fontSize = '26px';
  152. likeElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00';
  153. } else if (likeCount > 1000) {
  154. likeElement.style.fontSize = '28px';
  155. likeElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00, 0 0 20px #fffc00';
  156. }
  157. }
  158. });
  159.  
  160. // Also style comment counts
  161. const commentElements = document.querySelectorAll('.comment .engagement-count');
  162.  
  163. commentElements.forEach(function(commentElement) {
  164. const originalText = commentElement.innerText.trim();
  165. const commentCount = parseCount(originalText);
  166.  
  167. if (!isNaN(commentCount)) {
  168. // Reset existing custom styles
  169. commentElement.style.fontSize = '';
  170. commentElement.style.textShadow = '';
  171.  
  172. // Style based on number of comments
  173. if (commentCount >= 1 && commentCount <= 2) {
  174. commentElement.style.fontSize = '14px';
  175. } else if (commentCount >= 3 && commentCount <= 5) {
  176. commentElement.style.fontSize = '16px';
  177. commentElement.style.textShadow = '0 0 3px #00ffff';
  178. } else if (commentCount >= 6 && commentCount <= 10) {
  179. commentElement.style.fontSize = '18px';
  180. commentElement.style.textShadow = '0 0 4px #00ffff';
  181. } else if (commentCount >= 11 && commentCount <= 30) {
  182. commentElement.style.fontSize = '20px';
  183. commentElement.style.textShadow = '0 0 6px #00ffff, 0 0 10px #00ffff';
  184. } else if (commentCount > 30) {
  185. commentElement.style.fontSize = '22px';
  186. commentElement.style.textShadow = '0 0 8px #00ffff, 0 0 12px #00ffff, 0 0 16px #00ffff';
  187. }
  188. }
  189. });
  190. }
  191.  
  192. // Function to make interaction bars visible by manipulating the DOM
  193. function forceShowInteractionBars() {
  194. // Find all photo containers
  195. const photoContainers = document.querySelectorAll('.photo-list-photo-view');
  196.  
  197. photoContainers.forEach(container => {
  198. // Find the interaction bar in this container
  199. const interactionBar = container.querySelector('.interaction-bar');
  200.  
  201. if (interactionBar) {
  202. // Completely hide the original interaction bar
  203. interactionBar.style.display = 'none';
  204. interactionBar.style.opacity = '0';
  205. interactionBar.style.visibility = 'hidden';
  206. interactionBar.style.pointerEvents = 'none';
  207.  
  208. // Remove any padding we might have added before
  209. container.style.paddingBottom = '';
  210. container.style.marginBottom = '';
  211.  
  212. // Create a new div for displaying stats if it doesn't exist yet
  213. let statsDiv = container.querySelector('.always-visible-stats');
  214.  
  215. if (!statsDiv) {
  216. statsDiv = document.createElement('div');
  217. statsDiv.className = 'always-visible-stats';
  218.  
  219. // Apply explicit styles directly to the element
  220. statsDiv.style.position = 'absolute';
  221. statsDiv.style.bottom = '0';
  222. statsDiv.style.left = '0';
  223. statsDiv.style.right = '0';
  224. statsDiv.style.height = '25px';
  225. statsDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.75)';
  226. statsDiv.style.color = '#ffffff';
  227. statsDiv.style.display = 'flex';
  228. statsDiv.style.alignItems = 'center';
  229. statsDiv.style.justifyContent = 'space-around';
  230. statsDiv.style.zIndex = '9999';
  231. statsDiv.style.padding = '0';
  232.  
  233. // Get original elements
  234. const faveItem = interactionBar.querySelector('.engagement-item.fave');
  235. const commentItem = interactionBar.querySelector('.engagement-item.comment');
  236.  
  237. if (faveItem && commentItem) {
  238. // Create custom fave element with working interaction
  239. const customFaveItem = document.createElement('div');
  240. customFaveItem.className = 'custom-fave-item';
  241. customFaveItem.style.display = 'flex';
  242. customFaveItem.style.alignItems = 'center';
  243. customFaveItem.style.cursor = 'pointer';
  244. customFaveItem.style.padding = '0 10px';
  245.  
  246. // Create star icon
  247. const starIcon = document.createElement('span');
  248. starIcon.innerHTML = '★'; // Unicode star
  249. starIcon.style.color = '#fffc00'; // Yellow color
  250. starIcon.style.fontSize = '16px';
  251. starIcon.style.marginRight = '4px';
  252. starIcon.style.lineHeight = '1';
  253.  
  254. // Get original count
  255. const countSpan = faveItem.querySelector('.engagement-count');
  256. const customCountSpan = document.createElement('span');
  257. customCountSpan.className = 'custom-engagement-count';
  258. customCountSpan.textContent = countSpan ? countSpan.textContent : '0';
  259. customCountSpan.style.color = '#fffc00'; // Yellow color
  260.  
  261. // Add click event that triggers the original fave button
  262. customFaveItem.addEventListener('click', function(e) {
  263. e.stopPropagation(); // Prevent triggering photo click
  264. // Click the original fave button
  265. faveItem.click();
  266. // Update our custom count after a short delay
  267. setTimeout(() => {
  268. const updatedCount = faveItem.querySelector('.engagement-count');
  269. if (updatedCount) {
  270. customCountSpan.textContent = updatedCount.textContent;
  271. // Re-stylize after update
  272. stylizeCustomCounts();
  273. }
  274. }, 1000);
  275. });
  276.  
  277. // Assemble fave item
  278. customFaveItem.appendChild(starIcon);
  279. customFaveItem.appendChild(customCountSpan);
  280.  
  281. // Create custom comment element (non-interactive, just display)
  282. const customCommentItem = document.createElement('a');
  283. customCommentItem.className = 'custom-comment-item';
  284. customCommentItem.style.display = 'flex';
  285. customCommentItem.style.alignItems = 'center';
  286. customCommentItem.style.padding = '0 10px';
  287. customCommentItem.style.textDecoration = 'none';
  288.  
  289. // Set the href to the original comment link if available
  290. if (commentItem.hasAttribute('href')) {
  291. customCommentItem.href = commentItem.getAttribute('href');
  292. }
  293.  
  294. // Comment icon
  295. const commentIcon = document.createElement('span');
  296. commentIcon.innerHTML = '💬'; // Speech bubble emoji
  297. commentIcon.style.fontSize = '14px';
  298. commentIcon.style.marginRight = '4px';
  299. commentIcon.style.lineHeight = '1';
  300.  
  301. // Comment count
  302. const commentCountSpan = commentItem.querySelector('.engagement-count');
  303. const customCommentCountSpan = document.createElement('span');
  304. customCommentCountSpan.className = 'custom-comment-count';
  305. customCommentCountSpan.textContent = commentCountSpan ? commentCountSpan.textContent : '0';
  306. customCommentCountSpan.style.color = '#00ffff'; // Cyan color
  307.  
  308. // Assemble comment item
  309. customCommentItem.appendChild(commentIcon);
  310. customCommentItem.appendChild(customCommentCountSpan);
  311.  
  312. // Add both items to our custom stats bar
  313. statsDiv.appendChild(customFaveItem);
  314. statsDiv.appendChild(customCommentItem);
  315.  
  316. // Find the appropriate place to add our element
  317. const photoElement = container.querySelector('.photo-list-photo-container');
  318. if (photoElement) {
  319. photoElement.appendChild(statsDiv);
  320. } else {
  321. container.appendChild(statsDiv);
  322. }
  323. }
  324. }
  325. }
  326. });
  327.  
  328. // Apply styling to our custom counts
  329. stylizeCustomCounts();
  330. }
  331.  
  332. // Function to stylize our custom counts
  333. function stylizeCustomCounts() {
  334. // Style fave counts
  335. const customFaveCounts = document.querySelectorAll('.custom-engagement-count');
  336.  
  337. customFaveCounts.forEach(function(countElement) {
  338. const originalText = countElement.textContent.trim();
  339. const likeCount = parseCount(originalText);
  340.  
  341. if (!isNaN(likeCount)) {
  342. // Reset styles
  343. countElement.style.fontSize = '';
  344. countElement.style.textShadow = '';
  345.  
  346. // Apply styling based on count
  347. if (likeCount >= 1 && likeCount <= 5) {
  348. countElement.style.fontSize = '14px';
  349. } else if (likeCount >= 6 && likeCount <= 15) {
  350. countElement.style.fontSize = '16px';
  351. countElement.style.textShadow = '0 0 3px #fffc00';
  352. } else if (likeCount >= 16 && likeCount <= 30) {
  353. countElement.style.fontSize = '18px';
  354. countElement.style.textShadow = '0 0 4px #fffc00';
  355. } else if (likeCount >= 31 && likeCount <= 60) {
  356. countElement.style.fontSize = '20px';
  357. countElement.style.textShadow = '0 0 5px #fffc00';
  358. } else if (likeCount >= 61 && likeCount <= 100) {
  359. countElement.style.fontSize = '22px';
  360. countElement.style.textShadow = '0 0 6px #fffc00';
  361. } else if (likeCount >= 101 && likeCount <= 500) {
  362. countElement.style.fontSize = '24px';
  363. countElement.style.textShadow = '0 0 8px #fffc00, 0 0 12px #fffc00';
  364. } else if (likeCount >= 501 && likeCount <= 1000) {
  365. countElement.style.fontSize = '26px';
  366. countElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00';
  367. } else if (likeCount > 1000) {
  368. countElement.style.fontSize = '28px';
  369. countElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00, 0 0 20px #fffc00';
  370. }
  371. }
  372. });
  373.  
  374. // Style comment counts
  375. const customCommentCounts = document.querySelectorAll('.custom-comment-count');
  376.  
  377. customCommentCounts.forEach(function(countElement) {
  378. const originalText = countElement.textContent.trim();
  379. const commentCount = parseCount(originalText);
  380.  
  381. if (!isNaN(commentCount)) {
  382. // Reset styles
  383. countElement.style.fontSize = '';
  384. countElement.style.textShadow = '';
  385.  
  386. // Apply styling based on count
  387. if (commentCount >= 1 && commentCount <= 2) {
  388. countElement.style.fontSize = '14px';
  389. } else if (commentCount >= 3 && commentCount <= 5) {
  390. countElement.style.fontSize = '16px';
  391. countElement.style.textShadow = '0 0 3px #00ffff';
  392. } else if (commentCount >= 6 && commentCount <= 10) {
  393. countElement.style.fontSize = '18px';
  394. countElement.style.textShadow = '0 0 4px #00ffff';
  395. } else if (commentCount >= 11 && commentCount <= 30) {
  396. countElement.style.fontSize = '20px';
  397. countElement.style.textShadow = '0 0 6px #00ffff, 0 0 10px #00ffff';
  398. } else if (commentCount > 30) {
  399. countElement.style.fontSize = '22px';
  400. countElement.style.textShadow = '0 0 8px #00ffff, 0 0 12px #00ffff, 0 0 16px #00ffff';
  401. }
  402. }
  403. });
  404. }
  405.  
  406. // Main function to run on page load and after content changes
  407. function initialize() {
  408. // First, inject custom CSS to make bars visible
  409. injectCustomCSS();
  410.  
  411. // Also force show by manipulating DOM directly
  412. forceShowInteractionBars();
  413.  
  414. // No longer need this as we now use stylizeCustomCounts
  415. // stylizeLikeCounts();
  416. }
  417.  
  418. // Set up observer to watch for dynamically loaded content
  419. function setupObserver() {
  420. const observer = new MutationObserver(function(mutations) {
  421. mutations.forEach(function(mutation) {
  422. if (mutation.addedNodes.length) {
  423. // Wait a moment to let any dynamic content fully render
  424. setTimeout(stylizeLikeCounts, 300);
  425. }
  426. });
  427. });
  428.  
  429. // Start observing the document body for changes
  430. observer.observe(document.body, {
  431. childList: true,
  432. subtree: true
  433. });
  434. }
  435.  
  436. // Initial run
  437. document.addEventListener('DOMContentLoaded', function() {
  438. initialize();
  439. setupObserver();
  440. });
  441.  
  442. // Run immediately for already loaded content
  443. initialize();
  444. setupObserver();
  445.  
  446. // Run multiple times with delays to ensure we catch all dynamic content
  447. setTimeout(initialize, 500);
  448. setTimeout(initialize, 1500);
  449. setTimeout(initialize, 3000);
  450.  
  451. // Set up interval to periodically check and update
  452. setInterval(initialize, 5000);
  453.  
  454. // Additional force refresh on page interactions
  455. document.addEventListener('click', function() {
  456. setTimeout(initialize, 100);
  457. });
  458.  
  459. // Target specific events that might trigger content changes
  460. window.addEventListener('scroll', function() {
  461. // Debounce the scroll event
  462. clearTimeout(window.flickrStatsScrollTimer);
  463. window.flickrStatsScrollTimer = setTimeout(initialize, 300);
  464. });
  465. })();

QingJ © 2025

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