/* Retired 2008 Jan 19 22:35:09 GMT */ /* http://greasemonkey.mozdev.org */ // ==UserScript== // @name hystry comment tracker for news.yc // @namespace http://akkartik.name/greasemonkey // @description Highlight news.yc pages with new comments. Based on Waleed's bookmarklet at http://selfdebugging.com // @include http://news.ycombinator.com/* // ==/UserScript== // http://www.selfdebugging.com/2007/10/09/yc-news-tracker-a-better-way-to-read-comments // http://news.ycombinator.com/item?id=65307 /* Copyright (c) 2007, Waleed Abdulla, Kartik Agaram. All rights reserved. Code licensed under the MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var tracker={}; tracker.divID = "divCommentTracker"; tracker.tracklistDivID = "tracklist"; tracker.title = function() { var regTitle = /Hacker News \| *([^ ].*)/ var match = regTitle.exec(document.title) if (match) return match[1]; else return ""; } /* links on the current page => timestamp of newest comment in seconds. */ tracker.serverTimestamp = {} tracker.updateDOM = function() { var links = document.getElementsByTagName('a') for (var i=0; i < links.length; ++i) { if (links[i].innerHTML.search(/comments?|discuss/) == -1) continue var link = links[i].href.replace(/http:\/\/news.ycombinator.com\//, '') threadID = tracker.threadID(link) cookieTime = tracker.cookieTimestamp(threadID) if (!cookieTime) { continue } cookieTime/=1000 var serverTime = tracker.serverTimestamp[link] if (!serverTime) { /* No recent comments. Bug if this page has comments and server * crawler is in steady state. */ links[i].style.color = "#bbb" continue } if (serverTime >= cookieTime) { links[i].style.fontWeight="bold" links[i].style.color = "#ff6600" } else { links[i].style.color = "#bbb" } } } tracker.threadID = function(link) { var regUrl = /\?id=(\d+)/; var match = regUrl.exec(link); if (match) { return match[1] } return null } tracker.cookieTimestamp = function(threadID) { if (!threadID) return null var regCookie = new RegExp("TT"+threadID+"=(\\d+)") var match = regCookie.exec(document.cookie) if (match) return parseInt(match[1], 10) return null } tracker.main = function() { xhr = new GM_xmlhttpRequest({ 'method': 'get', 'url': 'http://hystry.com/newsyc/timestamps.cgi?'+window.location.href, 'onload': function(response) { tracker.serverTimestamp = eval('('+response.responseText+')') tracker.updateDOM() tracker.decideToTrack() } }) } tracker.decideToTrack = function() { // Get thread id from the URL. var threadID = tracker.threadID(window.location.href) var elem = document.getElementsByTagName('td') var topLevel = false for (var i = 0; i < elem.length; ++i) { if (elem[i].className == 'title') { topLevel = true break } } if (topLevel) { tracker.track(threadID) } } tracker.track = function(threadID) { // Minutes since this thread was last visited. -1 => not visited before. var minutesElapsed = -1 var now = new Date() var newComments = 0 // Get the time when the bookmarklet was clicked last from the cookie (if available). var cookieTime = tracker.cookieTimestamp(threadID) if (cookieTime) { var one_minute = 1000*60 minutesElapsed = Math.ceil((now.getTime() - cookieTime) / one_minute) cookieTime/=1000 } // Save the current time in the cookie. var expire = new Date() expire.setTime(now.getTime() + 1000 * 60 * 60 * 24 * 5) // 5 days /*? if(minutesElapsed == -1) // For reproducible debugging ?*/ document.cookie = tracker.title()+"TT" + threadID + "=" + escape(now.getTime()) + ";path=/;expires=" + expire.toGMTString() if (minutesElapsed == -1) { tracker.message(threadID, "Started tracking.", "#9F3F00") return } var link = window.location.href.replace(/http:\/\/news.ycombinator.com\//, '') var serverTime = tracker.serverTimestamp[link] if ( /* This should only ever happen in steady state if there are no * comments. */ !serverTime || serverTime < cookieTime ) { tracker.message(threadID, "Nothing new.", "#888"); return } // Find the comments that were added since last time. var spans = document.getElementsByTagName("span"); var regClass = new RegExp("\\bcomhead\\b"); var regTime = /((\d+)\sminute[s]?\sago)|((\d+)\shour[s]?\sago)|((\d+)\sday[s]?\sago)/; for (var i=0; i < spans.length; i++) if(regClass.test(spans[i].className)) { var match = regTime.exec(spans[i].innerHTML); if (match) { // Extract the time in minutes. var minutes = parseInt("0" + match[2],10) + (60 * parseInt("0" + match[4],10)) + (24 * 60 * parseInt("0" + match[6],10)); if (minutes < minutesElapsed) { spans[i].parentNode.style.fontWeight="bold"; newComments++; } } } tracker.message(threadID, newComments + " new comment(s).
", "#9F3F00"); } tracker.tracklist = function() { var cookies = document.cookie.split(";") var uniq = new Array() // This version disregards cookies created by previous versions without // the title field. //var regTitle = /([^ ].*)TT(\d+)=.*/; var regTitle = /(.*)TT(\d+)=.*/; for (var i=0; i < cookies.length; i++) { var match = regTitle.exec(cookies[i]) if (match) { if (!uniq[match[2]] || uniq[match[2]].length < cookies[i].length) uniq[match[2]] = cookies[i] } } var ans = "other pages tracked:"+ "" return ans } // Show the given message in a little orange box. tracker.message = function(threadID, message, color) { var sd = document.getElementById(tracker.divID) if (!sd) { var style = "float: right; width: 10em;margin-right: 30px;right:9%;background-color:#F4F4EA;cursor:default;font:normal 14px arial;padding:10px;z-index:999999;text-align:left;border:2px dotted #FF6600;" sd = document.createElement("div"); sd.id = tracker.divID; sd.onfocus = 'javascript:listTrackers()' sd.style.cssText = style; var commentBox = document.getElementsByTagName("textarea")[0] commentBox.parentNode.insertBefore(sd, commentBox) commentBox.parentNode.style.width='750px' } var html = "" html += "X" html += "
" + message + "
" html += "

"+tracker.tracklist() sd.innerHTML = html; } var head = document.getElementsByTagName("head")[0] // Show other pages tracked. var script = document.createElement('script') script.type = "text/javascript" script.innerHTML = "showTrackerList = function() {\n"+ " var sd = document.getElementById('"+tracker.tracklistDivID+"')\n"+ " sd.style.display = 'block'\n"+ "}" head.appendChild(script) // Delete one of them. var script = document.createElement('script') script.type = "text/javascript" script.innerHTML = "var now = new Date()\n"+ "deleteCookie = function(threadID) {\n"+ " var regCookie = new RegExp('[^;]*TT' + threadID + '=\\\\d+');\n"+ " match = regCookie.exec(document.cookie);\n"+ " document.cookie = match[0]+';path=/;expires='+now.toGMTString()\n"+ "}" head.appendChild(script) //? tracker.main()