<commit_before>// ==UserScript==
// @name           Endless Tweets
// @namespace      http://mislav.caboo.se/
// @description    Loads older tweets endlessly when you scroll on Twitter
// @include        http://twitter.com/*
// @exclude        http://twitter.com/help
// @exclude        http://twitter.com/help/*
// ==/UserScript==

(function(Twitter){
  // wrap everything in an anonymous function because this script doesn't run
  // in a wrapper when used in GreaseKit or other contexts other than Firefox
  
if (typeof GM_getValue == "function") {
  var getValue = GM_getValue
  var setValue = GM_setValue
} else {
  var Cookie = {
    PREFIX: '_greasekit_',
    prefixedName: function(name){
      return Cookie.PREFIX + name;
    },
    
    get: function(name) {
      var name = escape(Cookie.prefixedName(name)) + '='
      if (document.cookie.indexOf(name) >= 0) {
        var cookies = document.cookie.split(/\s*;\s*/)
        for (var i = 0; i < cookies.length; i++) {
          if (cookies[i].indexOf(name) == 0)
            return unescape(cookies[i].substring(name.length, cookies[i].length))
        }
      }
      return null
    },
    set: function(name, value, options) {
      newcookie = [escape(Cookie.prefixedName(name)) + "=" + escape(value)]
      if (options) {
        if (options.expires) newcookie.push("expires=" + options.expires.toGMTString())
        if (options.path)    newcookie.push("path=" + options.path)
        if (options.domain)  newcookie.push("domain=" + options.domain)
        if (options.secure)  newcookie.push("secure")
      }
      document.cookie = newcookie.join('; ')
    }
  }

  var getValue = function(name, defaultValue) {
    var value = Cookie.get(name)
    if (value) {
      if (value == 'true')  return true
      if (value == 'false') return false
      return value
    }
    else return defaultValue
  }
  var setValue = function(name, value) {
    var expiration = new Date()
    expiration.setFullYear(expiration.getFullYear() + 1)
    
    Cookie.set(name, value, { expires: expiration })
  }
}

var timeline  = $('timeline'),
    home      = window.location.pathname == '/home',
    debugMode = getValue('debugMode', false)

if (home) {
  var lastReadTweet = getValue('lastReadTweet', 0)
  var oldLastRead   = lastReadTweet
}

if (typeof GM_registerMenuCommand == "function") {
  GM_registerMenuCommand('Endless Tweets debug mode', function() {
    setValue('debugMode', (debugMode = !debugMode))
    alert('debug mode ' + (debugMode ? 'ON' : 'OFF'))
  })
}

if (timeline) {
  var nextPageLink = find('content', "div.pagination a[@rel='prev']"),
      timelineBody = $('timeline_body'),
      enablePreloading = true,
      loading = false,
      preloadingHandler = null
  
  if (home) {
    var cloneSource, polling = getValue('polling', false),
        currentUser = $('me_name').textContent
    
    function cloneExistingTweet() {
      if (!cloneSource || !cloneSource.parentNode) {
        var replyLink = find(timelineBody, '.actions a.repl')
        cloneSource = up(replyLink, 'tr')
      }
      return cloneSource.cloneNode(true)
    }
    
    function linkify(text) {
      return text.
        replace(/\b(https?:\/\/\S+?)([.,:;!?]?(?:\s|$))/g, '<a href="$1">$1</a>$2').
        replace(/(^|\W)@(\w+)/g, '$1@<a href="/$2">$2</a>')
    }
    
    function deliverUpdate(data) {
      var user = data.user
      var isCurrentUser = user.screen_name == currentUser
      // deep-clone an existing tweet and only change its contents
      var update = cloneExistingTweet()
      updateStatusInAttribute(update, 'id', data.id)
      update.className = 'hentry status'
      // user's thumbnail
      var thumb = find(update, '.thumb > a')
      var thumbImg = down(thumb)
      thumbImg.alt = user.name
      thumbImg.src = user.profile_image_url
      updateStatusInAttribute(thumb, 'href', user.screen_name)
      // main stuff: author and text
      var body = find(update, '.status-body')
      var name = find(body, 'strong a')
      name.href = thumb.href
      name.title = user.name
      name.firstChild.nodeValue = user.screen_name
      var text = find(body, '.entry-content')
      text.innerHTML = linkify(data.text)
      // metadata
      var meta = find(body, '.entry-meta')
      var date = new Date(data.created_at)
      meta.innerHTML = '<a href="http://twitter.com/' + user.screen_name + '/statuses/' + data.id +
        '" class="entry-date" rel="bookmark"><span class="published" title="">' +
        date.getHours() + ':' + date.getMinutes() + '</span></a> from ' + data.source
      
      // 'reply' icon
      var replyLink = find(update, '.actions a.repl')
      if (isCurrentUser) {
        // prevent user from replying to himself
        replyLink.parentNode.removeChild(replyLink)
      } else {
        updateStatusInAttribute(replyLink, 'title', user.screen_name)
        replyLink.href = '/home?status=@' + user.screen_name + '&in_reply_to_status_id=' + data.id
      }
      // if we accidentally cloned a faved link, reset that icon
      var favedLink = find(update, '.actions a.fav')
      if (favedLink) {
        favedLink.className = 'non-fav'
        favedLink.title = favedLink.title.replace('un-', '')
      }
      
    	// finally, insert the new tweet in the timeline ...
      timelineBody.insertBefore(update, timelineBody.rows[0])
      // ... and remove the oldest tweet from the timeline
      var oldestTweet = timeline.rows[timeline.rows.length - 1]
      oldestTweet.parentNode.removeChild(oldestTweet)
      
      // never send Growl notifications for own tweets
      if (window.fluid && !isCurrentUser) {
        var title = user.screen_name + ' updated ' + relativeTime(date) + ' ago'
        window.fluid.showGrowlNotification({
          title: title, description: data.text, icon: thumbImg,
          identifier: data.id, onclick: function() { window.fluid.activate() }
        })
      }
    }
    
    function updateStatusInAttribute(obj, prop, id) {
      var replacement, initial = obj.getAttribute(prop)
      if (typeof id == 'number') replacement = initial.replace(/([_\/])(\d{8,})\b/, '$1' + id)
      else replacement = initial.replace(/[\w-]+$/, id)
      obj.setAttribute(prop, replacement)
    }
    
    var checkUpdates = function() {
      xhr({
        url: 'http://twitter.com/statuses/friends_timeline.json?since_id=' + lastReadTweet,
        // url: 'http://twitter.com/statuses/friends_timeline.json?count=2',
        method: 'get',
        onerror: function(req) { alert('ERROR ' + req.status) },
        onload: function(req) {
          var data, updates = eval(req.responseText)
          for (var i = updates.length - 1; i >= 0; i--) {
            data = updates[i]
            // only show the update if an element with that status ID is not already present
            if (!$('status_' + data.id)) deliverUpdate(data)            
          }
          if (data) setValue('lastReadTweet', (lastReadTweet = data.id))
        }
      })
    }
    
    var pollInterval = null
    
    var startPolling = function() {
      pollInterval = setInterval(checkUpdates, 120 * 1000)
    }
    
    if (polling) startPolling()
    
    var control = $('device_control')
    var label = $E('label')
    var pollToggle = $E('input', { type: 'checkbox' })
    pollToggle.checked = polling
    label.appendChild(pollToggle)
    label.appendChild(document.createTextNode(' update every 2 minutes'))
    control.appendChild($E('br'))
    control.appendChild(label)
    
    pollToggle.addEventListener('change', function(e) {
      if (pollToggle.checked) {
        if (!pollInterval) {
          checkUpdates()
          startPolling()
        }
      } else {<commit_msg>live updates: max 4 Growl notifications, newest update first<commit_after>// ==UserScript==
// @name           Endless Tweets
// @namespace      http://mislav.caboo.se/
// @description    Loads older tweets endlessly when you scroll on Twitter
// @include        http://twitter.com/*
// @exclude        http://twitter.com/help
// @exclude        http://twitter.com/help/*
// ==/UserScript==

(function(Twitter){
  // wrap everything in an anonymous function because this script doesn't run
  // in a wrapper when used in GreaseKit or other contexts other than Firefox
  
if (typeof GM_getValue == "function") {
  var getValue = GM_getValue
  var setValue = GM_setValue
} else {
  var Cookie = {
    PREFIX: '_greasekit_',
    prefixedName: function(name){
      return Cookie.PREFIX + name;
    },
    
    get: function(name) {
      var name = escape(Cookie.prefixedName(name)) + '='
      if (document.cookie.indexOf(name) >= 0) {
        var cookies = document.cookie.split(/\s*;\s*/)
        for (var i = 0; i < cookies.length; i++) {
          if (cookies[i].indexOf(name) == 0)
            return unescape(cookies[i].substring(name.length, cookies[i].length))
        }
      }
      return null
    },
    set: function(name, value, options) {
      newcookie = [escape(Cookie.prefixedName(name)) + "=" + escape(value)]
      if (options) {
        if (options.expires) newcookie.push("expires=" + options.expires.toGMTString())
        if (options.path)    newcookie.push("path=" + options.path)
        if (options.domain)  newcookie.push("domain=" + options.domain)
        if (options.secure)  newcookie.push("secure")
      }
      document.cookie = newcookie.join('; ')
    }
  }

  var getValue = function(name, defaultValue) {
    var value = Cookie.get(name)
    if (value) {
      if (value == 'true')  return true
      if (value == 'false') return false
      return value
    }
    else return defaultValue
  }
  var setValue = function(name, value) {
    var expiration = new Date()
    expiration.setFullYear(expiration.getFullYear() + 1)
    
    Cookie.set(name, value, { expires: expiration })
  }
}

var timeline  = $('timeline'),
    home      = window.location.pathname == '/home',
    debugMode = getValue('debugMode', false)

if (home) {
  var lastReadTweet = getValue('lastReadTweet', 0)
  var oldLastRead   = lastReadTweet
}

if (typeof GM_registerMenuCommand == "function") {
  GM_registerMenuCommand('Endless Tweets debug mode', function() {
    setValue('debugMode', (debugMode = !debugMode))
    alert('debug mode ' + (debugMode ? 'ON' : 'OFF'))
  })
}

if (timeline) {
  var nextPageLink = find('content', "div.pagination a[@rel='prev']"),
      timelineBody = $('timeline_body'),
      enablePreloading = true,
      loading = false,
      preloadingHandler = null
  
  if (home) {
    var cloneSource, polling = getValue('polling', false),
        currentUser = $('me_name').textContent,
        growls = window.fluid ? [] : null
    
    function cloneExistingTweet() {
      if (!cloneSource || !cloneSource.parentNode) {
        var replyLink = find(timelineBody, '.actions a.repl')
        cloneSource = up(replyLink, 'tr')
      }
      return cloneSource.cloneNode(true)
    }
    
    function linkify(text) {
      return text.
        replace(/\b(https?:\/\/\S+?)([.,:;!?]?(?:\s|$))/g, '<a href="$1">$1</a>$2').
        replace(/(^|\W)@(\w+)/g, '$1@<a href="/$2">$2</a>')
    }
    
    function deliverUpdate(data) {
      var user = data.user
      var isCurrentUser = user.screen_name == currentUser
      // deep-clone an existing tweet and only change its contents
      var update = cloneExistingTweet()
      updateStatusInAttribute(update, 'id', data.id)
      update.className = 'hentry status'
      // user's thumbnail
      var thumb = find(update, '.thumb > a')
      var thumbImg = down(thumb)
      thumbImg.alt = user.name
      thumbImg.src = user.profile_image_url
      updateStatusInAttribute(thumb, 'href', user.screen_name)
      // main stuff: author and text
      var body = find(update, '.status-body')
      var name = find(body, 'strong a')
      name.href = thumb.href
      name.title = user.name
      name.firstChild.nodeValue = user.screen_name
      var text = find(body, '.entry-content')
      text.innerHTML = linkify(data.text)
      // metadata
      var meta = find(body, '.entry-meta')
      var date = new Date(data.created_at)
      meta.innerHTML = '<a href="http://twitter.com/' + user.screen_name + '/statuses/' + data.id +
        '" class="entry-date" rel="bookmark"><span class="published" title="">' +
        date.getHours() + ':' + date.getMinutes() + '</span></a> from ' + data.source
      
      // 'reply' icon
      var replyLink = find(update, '.actions a.repl')
      if (isCurrentUser) {
        // prevent user from replying to himself
        replyLink.parentNode.removeChild(replyLink)
      } else {
        updateStatusInAttribute(replyLink, 'title', user.screen_name)
        replyLink.href = '/home?status=@' + user.screen_name + '&in_reply_to_status_id=' + data.id
      }
      // if we accidentally cloned a faved link, reset that icon
      var favedLink = find(update, '.actions a.fav')
      if (favedLink) {
        favedLink.className = 'non-fav'
        favedLink.title = favedLink.title.replace('un-', '')
      }
      
    	// finally, insert the new tweet in the timeline ...
      timelineBody.insertBefore(update, timelineBody.rows[0])
      // ... and remove the oldest tweet from the timeline
      var oldestTweet = timeline.rows[timeline.rows.length - 1]
      oldestTweet.parentNode.removeChild(oldestTweet)
      
      // never send Growl notifications for own tweets
      if (growls && !isCurrentUser) {
        var title = user.screen_name + ' updated ' + relativeTime(date) + ' ago'
        growls.push({
          title: title, description: data.text, icon: thumbImg,
          identifier: 'tw' + data.id, onclick: function() { window.fluid.activate() }
        })
      }
    }
    
    function updateStatusInAttribute(obj, prop, id) {
      var replacement, initial = obj.getAttribute(prop)
      if (typeof id == 'number') replacement = initial.replace(/([_\/])(\d{8,})\b/, '$1' + id)
      else replacement = initial.replace(/[\w-]+$/, id)
      obj.setAttribute(prop, replacement)
    }
    
    var debug = false // temp set to true for testing purposes
    
    var checkUpdates = function() {
      if (debug)
        var url = 'http://twitter.com/statuses/friends_timeline.json?count=5'
      else
        var url = 'http://twitter.com/statuses/friends_timeline.json?since_id=' + lastReadTweet
      
      xhr({
        url: url,
        method: 'get',
        onerror: function(req) { alert('ERROR ' + req.status) },
        onload: function(req) {
          var data, updates = eval(req.responseText)
          for (var i = updates.length - 1; i >= 0; i--) {
            data = updates[i]
            // only show the update if an element with that status ID is not already present
            if (debug || !$('status_' + data.id)) deliverUpdate(data)
          }
          if (growls) {
            var limit = growls.length - 4
            for (var i = growls.length - 1; i >= 0; i--) {
              if (i == limit) {
                window.fluid.showGrowlNotification({
                  title: '(' + limit + ' more update' + (limit > 1 ? 's' : '') + ')',
                  description: '',
                  onclick: function() { window.fluid.activate() }
                })
                break
              }
              window.fluid.showGrowlNotification(growls[i])
            }
            growls = []
          }
          if (data) setValue('lastReadTweet', (lastReadTweet = data.id))
        }
      })
    }
    
    var pollInterval = null
    
    var startPolling = function() {
      pollInterval = setInterval(checkUpdates, (debug ? 12 : 120) * 1000)
    }
    
    if (polling) startPolling()
    
    var control = $('device_control')
    var label = $E('label')
    var pollToggle = $E('input', { type: 'checkbox' })
    pollToggle.checked = polling
    label.appendChild(pollToggle)
    label.appendChild(document.createTextNode(' update every 2 minutes'))
    control.appendChild($E('br'))
    control.appendChild(label)
    
    pollToggle.addEventListener('change', function(e) {
      if (pollToggle.checked) {
        if (!pollInterval) {
          checkUpdates()
          startPolling()
        }
      } else {
{'fraction_non_alphanumeric': 0.14194184327254805, 'fraction_numerical': 0.003634795465746673, 'mean_word_length': 2.4304733727810652, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '350651', 'n_tokens_mistral': 5024, 'n_tokens_neox': 4902, 'n_words': 1172}