Script AutoUpdater

By Eyal Soha Last update Jul 1, 2009 — Installed 18,914 times. Daily Installs: 27, 25, 16, 11, 27, 18, 31, 13, 15, 17, 21, 23, 6, 12, 33, 15, 8, 14, 11, 18, 11, 17, 14, 16, 12, 12, 12, 13, 13, 9, 8, 14

There are 8 previous versions of this script.

// ==UserScript==
// @name           Script AutoUpdater
// @namespace      userscripts.org/eyalsoha
// @description    checks all your scripts for updates on user script.org
// @include        *
// ==/UserScript==

//This is just a rip-off of alien_scum's script updater but with patches to make it work better
(
function () {
  function getValue(query) {
    return eval(GM_getValue('cache','({})'))[query];
  }

  function setValue(query,value) {
    var values=eval(GM_getValue('cache','({})'));
    values[query]=value;
    GM_setValue('cache', uneval(values));
  }

  function $x(xpath) { 
    //xpath=xpath.replace(/((^|\|)\s*)([^/|]+)/g,'$2//$3').replace(/([^.])\.(\w*)/g,'$1[@class="$2"]').replace(/#(\w*)/g,'[@id="$1"]').replace(/\/\[/g,'/*[');
    var got=document.evaluate(xpath,document,null,null,null), result=[];
    while(next=got.iterateNext()) result.push(next);
    if (x=result[0]) for(var i in x.wrappedJSObject) if(typeof result[i] =='undefined')
      if(typeof x[i]=='function') result[i]=Function('a','b','c','this.forEach(res=function (n) {if(n.'+i+') return n.'+i+'(a,b,c);return res})');
      else {result[i]=x[i]; result.watch(i,function(p,o,n){this.forEach(function (e){e[p]=n})});}
    result.addEventListener=function(t,f,b){this.forEach(function(n){n.addEventListener(t,f,b)})};
    return result;
  }



  function xhr(uri,f,a,b,c,d) {GM_xmlhttpRequest({method: 'get',headers: {'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey'},url: uri,onload:function(res){f(res.responseText,a,b,c,d)}});}
  function iframe(src,show) {var f=document.createElement('iframe');f.style.display=show?'inline':'none';f.src=src;return f;}

  function hash(s){
    s=s.replace(/&/g,'&').replace(/&\w*;/g,'').replace(/[^-\w(){}=.+]/g,'');
    var h=0;
    for (i=0;i<s.length;i++) h=(h<<3)^(h>>29)^s.charCodeAt(i);
    return h 
  }

  function wait(c,f){
    if (c()) f()
    else window.setTimeout(function (){wait(c,f)},1000,false);
  }

  function upref(num) {return 'http://userscripts.org/scripts/source/'+num+'.user.js';}
  function ckref(num) {return upref(num)+'?source';}
  function showref(num) {return 'http://userscripts.org/scripts/show/'+num;}

  function cE(tag,content){
    var res=document.createElement(tag);
    if(content) res.innerHTML=content;
    return res;
  }


  function checkable() {
    try {
      // avoid a flood of dialogs e.g. when opening a browser with multiple tabs set to homepage
      // and a script with * includes or opening a tabgrop
      var DoS_PREVENTION_TIME = 2 * 60 * 1000;
      var isSomeoneChecking = getValue('CHECKING', null);
      var now = new Date().getTime();
      setValue('CHECKING', now.toString());
      if (isSomeoneChecking && (now - isSomeoneChecking) < DoS_PREVENTION_TIME) return false;

      // check daily
      var ONE_DAY = 24 * 60 * 60 * 1000;
      var lastChecked = getValue('LAST_CHECKED', null);
      if (lastChecked && (now - lastChecked) < ONE_DAY) return false;
      setValue('LAST_CHECKED',now);
      return true
    } catch (ex) {return false}
  }

  if(GM_registerMenuCommand) {
    GM_registerMenuCommand("Check for script updates", function() {GM_openInTab("about:cache#scriptupdater")});
  }

  profile=getValue('profile');
  if (!profile) {
    setValue('profile',true);
    GM_openInTab('about:cache#scriptupdater') //get profile directory from cache page
  } else {
    if(location.href=='about:cache#scriptupdater') {
      profile=document.getElementsByTagName('tt')[7].innerHTML.replace(/\\/g,'/').replace(/^\s*/g,'').replace(/ /g,'%20').replace(/cache$/i,'').replace(/<wbr>/g,'')
      if (/\w:\//.test(profile)){
        if(/\/local%20settings/i.test(profile))
          profile='file:///'+profile.replace(/\/local%20settings/i,'');
        else if (/\/AppData\/Local/.test(profe))
            profile='file:///'+profile.replace(/\/AppData\/Local/i,'\/AppData\/Roaming');
          else          
            profile='file:///'+profile.replace(/((\/[^/]*){2})\/[^/]*/i,'$1');
      }
      else 
        profile='file://'+profile.replace(/Caches(\/Firefox)/i,"Application%20Support$1");
      setValue('profile',profile);
      location.replace(profile+'bookmarks.html#scriptupdater'); //we need a file:/// page to get acsess to the scripts
    }
    if(location.href==profile+'bookmarks.html#scriptupdater') {
      document.body.innerHTML='';
      document.title='Update userscripts';
      GM_addStyle('#header td:hover {background:#EEEEEE}'+
                  '#header {cursor:pointer}');
      mes=document.body.appendChild(document.createElement('h2'));
      mes.innerHTML='finding scripts...';
      var config_iframe;
      f=document.body.appendChild(config_iframe=iframe(profile+'gm_scripts/config.xml'));
      window.setTimeout(function (){// waits for iframe to load
        d=f.contentDocument.documentElement;
        t=document.body.appendChild(document.createElement('table')).appendChild(document.createElement('tbody'));
        lnks=[];
        tr=t.appendChild(cE('tr'));
        tr.id='header';
        tr.appendChild(cE('td','Name'));
        tr.appendChild(cE('td','Filename'));
        tr.appendChild(cE('td','State'));
        tr.appendChild(cE('td','Hash')).style.display='none';
        tr.appendChild(cE('td','Ignore Updates'));
        var cnt=0;
        var cnt2=0;
        var scripts={};
        var oldscripts=getValue('scripts');
        var ignores;
        ignores = getValue('ignores') || {};
        for (var i=0;n=d.childNodes[i];i++) if(n.nodeType==1 && n.nodeName=="Script") {
          tr=t.appendChild(cE('tr'));
          cnt++;
          cnt2++;
          tr.appendChild(cE('td',name=n.getAttribute('name')));
          tr.appendChild(cE('td',filename=n.getAttribute('filename')));
          basedir=n.getAttribute('basedir');
          if(n.getAttribute('enabled')=='false'){
            tr.style.background='#EEEEEE';
            tr.appendChild(cE('td','disabled')); 
            cnt--;
          } else {
            xhr('http://userscripts.org/scripts/search?q='+encodeURIComponent(name),function(res,p,s,ns) {
              var oldname = s;
              if (s.length>47) s=s.slice(0,47)+'...';
              var xs = s.replace(/\&/g, '&amp;').replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
              reg=new RegExp('<a[^>]*href[^>\\d]*/show/(\\d+)[^>]*>[\s\r\n]*'+xs+'[\s\r\n]*<\\\/a>','ig');
              var r=null,rs=[];
              while(e=reg.exec(res)) rs.push(e);
              if (rs.length>1) //if there is more than one script with the same name!  :(
                rs.forEach(function(n){xhr(ckref(n[1]),function(res,ns,n,oldname){
                  var m=res.match(/@namespace\s*(.*)/)
                  if (((!m&&!ns)||(m&&m[1]==ns)) && //if the namespace is correct and
                      (r==null ||                   //(we don't yet have a result or this result is better)
                       (oldscripts != null && oldscripts[oldname] != null && n[1] == oldscripts[oldname].num))) {
                    r=n;
                  }
                  rs.splice(rs.indexOf(n),1);
                },ns,n,oldname);}); 
              else {
                r=rs[0];
                rs=[];
              }
              wait(function (){return(rs.length==0)},function (){
                if (r) {
                  p.innerHTML=r[1];
                  p.parentNode.childNodes[0].innerHTML = '<a href="' + showref(r[1]) + '">' + p.parentNode.childNodes[0].innerHTML + '</a>';
                  scripts[s]={name:s,num:r[1],hash:p.nextSibling.innerHTML};
                  xhr(ckref(r[1]),function(res) {
                    if(!(c=hash(res)==p.nextSibling.innerHTML))
                      p.innerHTML='<a href="'+upref(p.innerHTML)+'">Update</a>';
                    p.nextSibling.innerHTML+='<br>'+hash(res);
                    p.parentNode.style.background=c?'#CCFFCC':'#FFAA44';
                  });
                } else {
                  p.innerHTML='Not on us.o';
                  p.parentNode.style.background='#FFCCCC';
                }
                cnt--;
              });
            },tr.appendChild(cE('td')),name,n.getAttribute('namespace'));
          }
          var current_iframe;
          tr.appendChild(cE('td')).appendChild(current_iframe=iframe(profile+'gm_scripts/'+basedir+'/'+filename)).parentNode.style.display='none';
          current_iframe.addEventListener('load',function(){
            this.parentNode.parentNode.childNodes[3].innerHTML=hash(this.contentDocument.documentElement.innerHTML.match(/<pre>([\s\S]*)<\/pre>/)[1]);
            cnt2--;},false);
          var checkbox;
          checkbox = tr.appendChild(cE('td', '<input name="' + name + '" type="checkbox"' + ((ignores[name])?' checked':'') + '>'));
          checkbox.firstChild.addEventListener('click',function(){
            ignores = getValue('ignores') || {};
            ignores[this.name] = this.checked;
            setValue('ignores', ignores);
          }, false);
        }
        mes.innerHTML='looking on us.o for updates...';
        wait(function(){return cnt==0 && cnt2==0},function (){//waits for iframe to load
          mes.innerHTML='Updates Found';
          f.parentNode.removeChild(f);
          setValue('scripts',scripts);
          [].forEach.call(t.childNodes[0].childNodes,function (td,i) {
            td.addEventListener('click',function(){
              nodes=[];
              while(t.childNodes[1]) nodes.push(t.removeChild(t.childNodes[1]));
              nodes.sort(function(a,b){return a.childNodes[i].innerHTML.toLowerCase()<b.childNodes[i].innerHTML.toLowerCase()?1:-1;})
              while(script = nodes.pop()) t.appendChild(script);
            },false);
          });    
        });
     },30,false);
    }
    if(/userscripts.org/.test(location.href)){
      $x('//a[contains(@href,".user.js")]').addEventListener('click',function(){var num=this.href.match(/\d+/)[0];xhr(ckref(num),function(res,num){
        scripts=getValue('scripts');
        name=res.match(/@name\s*(.*)/)[1];
        scripts[name]={name:name,num:num,hash:hash(res)};
        setValue('scripts',scripts);
      },num)});
    }
    if(!/#scriptupdater/.test(location.href) && checkable()) {
      scripts=getValue('scripts') || {};
      ignores=getValue('ignores') || {};
      for(s in scripts){
        if(ignores[s]) continue;
        xhr(ckref(scripts[s].num),function(res,s,script_name,script_hash,script_num){
          if (hash(res)!=script_hash) {
            scpts=getValue('scripts');
            scpts[s].hash=hash(res);
            setValue('scripts',scpts);
            if(confirm('Install update found for:\n'+script_name+'\n'+res.match(/@description\s*(.*)/)[1])) 
              GM_openInTab(upref(script_num))
            else if(confirm('The update was not installed.\n\nIgnore future updates for:\n'+script_name+'\n'+res.match(/@description\s*(.*)/)[1])) {
              my_ignores = getValue('ignores');
              ignores[script_name] = true;
              setValue('ignores',ignores);
              alert('If you change your mind, visit:\nabout:cache#scriptupdater');
            }
          }
        },s,scripts[s].name,scripts[s].hash,scripts[s].num);
      }
    }
  }
})();