SelfUpdaterExampleOpera

By ScroogeMcPump Last update Nov 21, 2008 — Installed 66 times. Daily Installs: 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

There are 2 previous versions of this script.


// ==UserScript==
// @name        SelfUpdaterExampleOpera
// @shortname   SUpE
// @namespace   tag:GasBuddyPhilly@yahoo.com,2008-05:monkey
// @description Template for constructing self-updating user scripts.
// @released    2008/11/21 14:08:56
// @frequency   10 hours
// @releaseURL  http://userscripts.org/scripts/show/99999999
// @scriptURL   http://userscripts.org/scripts/source/29880.user.js
// @releaseURL  http://userscripts.org/scripts/source/29880.meta.js
// @scriptURL   http://userscripts.org/scripts/source/29880.user.js
// @releaseURL  http://userscripts.org/scripts/review/29880?format=txt
// @scriptURL   http://userscripts.org/scripts/source/29880.user.js
// @releaseURL  http://userscripts.org/scripts/source/29880.user.js?
// @scriptURL   http://userscripts.org/scripts/source/29880.user.js
// @include     *
// ==/UserScript==

//a duplicate copy of the UserScript metadata, in a JavaScript string
//(Opera is unable to read this data otherwise)
const SUpE_META=
 '// ==UserScript==\n'+
 '// @name        SelfUpdaterExampleOpera\n'+
 '// @shortname   SUpE\n'+
 '// @namespace   tag:GasBuddyPhilly@yahoo.com,2008-05:monkey\n'+
 '// @description Template for constructing self-updating user scripts.\n'+
 '// @released    2008/11/21 14:08:56\n'+
 '// @frequency   10 hours\n'+
 '// @releaseURL  http://userscripts.org/scripts/show/99999999\n'+
 '// @scriptURL   http://userscripts.org/scripts/source/29880.user.js\n'+
 '// @releaseURL  http://userscripts.org/scripts/source/29880.meta.js\n'+
 '// @scriptURL   http://userscripts.org/scripts/source/29880.user.js\n'+
 '// @releaseURL  http://userscripts.org/scripts/review/29880?format=txt\n'+
 '// @scriptURL   http://userscripts.org/scripts/source/29880.user.js\n'+
 '// @releaseURL  http://userscripts.org/scripts/source/29880.user.js?\n'+
 '// @scriptURL   http://userscripts.org/scripts/source/29880.user.js\n'+
 '// @include     *\n'+
 '// ==/UserScript==\n'+
 '';//end of metadata

/*-=-=-=- Documentation

This script does nothing - yet.

This script is intended as a template; if and when I get it to work in
 Opera, if you follow these instructions, and include the code from this
 script in your own scripts, your scripts will periodically (at an
 interval configured by the user, or, if not configured, specified as a
 default within the script) connect to one or more predefined locations,
 looking for more recent versions of itself; and, if one is found, it
 will offer to redirect the user to the new version's location, thereby
 allowing the user to save and install it.  (Unlike Greasemonkey, Opera
 has no automated user script installer.)

IMPROVED(?):  Instead of putting tons of messages in the Error Console,
 bugging you about updates with every new site you visit, and (if you
 have that stupid aa-gm-functions script installed) permanently
 obscuring the upper right corner of the page, this version merely(!)
 fails silently.

The design goals for this were to create a self-updater routine that:
- works in Firefox 3.  (That was by far the easy part.)
- is more compact and less complex than the "User Script Updates" script
 at <http://userscripts.org/scripts/show/2296>.  (I'm not sure I
 succeeded on that second point, but this script is half the size of
 that one - and half of this one is this documentation!)
- is self-contained, i.e., doesn't require the installation of a second
 script to get the updating functionality.  (Opera developers seem to
 find this acceptable; I do not.) 
- leverages the existing method of specifying script metadata, as
 opposed to requiring that such data be duplicated in a JavaScript
 object.  (Unfortunately, this is one of the many things that is proving
 to be impossible in Opera.)

//-=-=- Metadata

At the top of this script are the normal Greasemonkey ==UserScript==
 declarations, along with some new lines exclusive to this script.  In
 addition, the entire section is duplicated as a JavaScript string and
 assigned to the SUpE_META constant, to make the metadata visible to
 the script; without it, these lines are ignored at run time, just like
 any other comments.  You must include this duplicate version of your
 own script's metadata for this code to work.  (Firefox has a trick that
 can make the metadata visible without requiring this duplication, but
 it doesn't work in Opera, so please be extra careful that these two
 sections match exactly; I use a makefile of sorts that does the
 copying for me.)  When the script is run, the contents of this constant
 are parsed for the relevant keywords, which are:

@shortname - contains an abbreviated version of this script's name; used
 in the User Script Commands menu to distinguish the commands for this
 script from those of others that may also be running, without unduly
 increasing the menu's width.  Ideally, this should be no more than
 three or four characters; if it's not present, the full @name will be
 used.

@released - contains a full or partial date/time representing the
 release date of the currently running version of this script.
 ("Version numbers" are not used because parsing them would add
 complexity.)  The date can be in any format the Date object
 understands.  (Of course, you should change this to the release date
 for YOUR script.)

@frequency - contains the default interval at which the script should
 check for updates.  If more than this amount of time has passed since
 the last invocation of this script, the update check is triggered.  The
 format for this field is a positive integer, followed by whitespace,
 followed by one of "minute", "minutes", "hour", "hours", "day", "days",
 "week", "weeks", "month", or "months" (case insensitive).  The user may
 specify a different interval, either at the first invocation, or by
 selecting "Set Update Interval" from the User Script Commands.

@releaseURL and @scriptURL - normally only one of each would be
 specified, but the script provides for downloading from alternate
 locations, in case the primary location is unavailable.  The number of
 @releaseURLs specified should always be the same as the number of
 @scriptURLs.  The script will NOT cycle thru these; the second set, if
 present, will only be tried if the first one fails.  The script also
 assumes that if @releaseURL is valid, the corresponding @scriptURL will
 be, too.  (Again, you should change these to point to your own script's
 URLs.)

@releaseURL - specifies a location in which to look for update
 information for this script.  It can be a pointer to the script itself
 (in which case @scriptURL would be identical), but need not be; this is
 to allow for testing the currency of a script without inflating its
 download count, and without wasting time and bandwidth downloading the
 entirety of the script.  Any file allowing for text in the
 ==UserScript== format will do for this purpose - and the only
 information required in this file is the @released line.  If this is
 not found in the file, the update check will move on to the next URL
 pair; if there are no more pairs, it'll inform the user that it was
 unable to do the update check, and offer to let the user change the
 update interval.

@scriptURL - specifies a location at which the latest version of this
 script can be found.  If the corresponding @releaseURL shows that a
 newer version is available, this is where it will be downloaded from
 (if the user allows it).

All of these keywords are referenced only within the script itself, so
 they can be changed to anything you want, as long as all references to
 them are changed accordingly; however, they were chosen to avoid
 conflicts with other unofficial keywords already known to be used, so
 this should not be necessary.

The script expects all of these keywords, plus @name, to be present in
 the ==UserScript== block, and does little or no sanity checking on them
 (to keep it small and fast), so double check all of these entries
 before publishing your script.

//-=-=- Script Outline

The script code is largely uncommented, to make it easier to copy into
 your script, so a step by step explanation is provided here instead.

The first thing the script does is create string constants for the text
 used in menu items and dialog boxes; these can be translated into your
 preferred language, if desired.  (The end of the group for which l10n
 is encouraged is clearly marked.)

Next, the script parses the SUpE_META data, as explained above; then it
 adds two commands to the Greasemonkey menu ("Check For Update Now" and
 "Set Update Interval"), and looks for its configuration settings
 (SUpE_LastUpdateCheck and SUpE_UpdateFrequency).  If
 SUpE_LastUpdateCheck is missing or invalid, it'll assume that an update
 check is due immediately; if SUpE_UpdateFrequency is missing or
 invalid, it'll assume that this is the first time running the script,
 and prompt the user to either accept the script's interval, or enter a
 different one.

After this, it adds SUpE_UpdateFrequency to SUpE_LastUpdateCheck, and if
 the resulting point in time has already passed, it ends by calling the
 function that initiates the automatic update check - the same function
 called when the user manually requests a check.  (Note that since
 Internet data fetching must be done asynchronously within Greasemonkey,
 all further code must be contained within the check function - or, more
 specifically, within the callback function that's run when a data fetch
 is completed.)

If an update is called for (either manually or automatically), the first
 thing the update routine does is reset the update clock, so that other
 pages using this script that are loaded while the check is in progress
 don't attempt to do their own checks simultaneously.  Then, it passes
 control to the same routine that is used as the callback function when
 a fetch is (successfully or unsuccessfully) completed.

The remainder of the code is essentially self-documented, in that it
 carefully tests the response (if any) received from each URL request,
 and extensively logs those tests to the Error Console.

Finally, once the outcome of the update check is known (either "found
 newer", "found but not newer", or "not found"), the update variables
 are reset, just in case the user calls for another update check
 manually, before leaving this same page.

To avoid name collisions, the self-updater code is encapsulated in the
 SUpE_SelfUpdater function.  The only other names visible outside of
 this function are the SUpE_META constant created above, and the
 SUpE_LastUpdateCheck and SUpE_UpdateFrequency parameters (but
 Greasemonkey automatically prepends the @namespace and @name values to
 these before storing them, so name collisions here are almost
 impossible).

*/

//-=-=-=- Code (copy everything below this point into your script)
SUpE_SelfUpdater();
//Self-updater function is copyright <GasBuddyPhilly@yahoo.com>,
// located at <http://userscripts.org/scripts/show/29880>,
// and licensed under <http://gnu.org/licenses/gpl-3.0.html>,
// incorporated herein by reference.
function SUpE_SelfUpdater()
{//localizable strings
 const CKNOW='Check Now For ';
 const UPDTE=' Update';
 const SETTT='Set ';
 const UPIVL=' Update Interval';
 const ITAPP='It appears this is the first time you\'ve used\n'+
  'the ';
 const ISDES=' script.\n'+
  'This script is designed to automatically\n'+
  'check for updates every ';
 const CHIVL='.\n'+
  'Would you like to change this interval?'
 const HWLNG='How long would you like the ';
 const WAITB='\n'+
  'script to wait between update checks?\n'+
  'Enter an interval such as "12 hours" or "1 week".';
 const DIDNT='I didn\'t understand that.\n\n';
 const UPIN4='The update interval for the ';
 const REMAI='\n'+
  'script remains set at ';
 const UMAYS='.  You may\n'+
  'select "';
 const FRMTH='" from the\n'+
  '"User Script Commands..." menu at any time\n'+
  'to change this.';
 const THRIS='There is an update available for the\n';
 const GRSCR=' Greasemonkey script.';
 const INSIT='\n'+
  'Would you like to install it?';
 const UWILB='You will be reminded about this update again\n'+
  'in ';
 const THRNO='There is no update available for the\n';
 const SELFU='The self-updater for the ';
 const WASUN='\n'+
  'script was unable to locate any valid update\n'+
  'information.  This could mean that this\n'+
  'computer has lost its Internet connection, or\n'+
  'that the original site for this script has gone\n'+
  'down, moved, or disappeared.\n\n'+
  'This script will check again in '
 //l10n not recommended
 const S_LU='SUpE_LastUpdateCheck';
 const S_UF='SUpE_UpdateFrequency';
 const INVDT='Invalid Date';

 var parms=parseParms(SUpE_META);
 if(!parms.shortname)
 {parms.shortname=parms.name;
 };
 GM_registerMenuCommand
 (SETTT+parms.shortname+UPIVL,
  function()
  {GM_setValue(S_UF,askUF(parms,GM_getValue(S_UF)));
  }
 );
 GM_registerMenuCommand
 (CKNOW+parms.shortname+UPDTE,
  function()
  {parms.m=true;
   doUpCk();
  }
 );
 var LU=new Date(GM_getValue(S_LU,''));
 if(LU.toString()==INVDT)
 {LU=new Date(0);
  GM_setValue(S_LU,LU.toString());
 };
 var UF=GM_getValue(S_UF,'');
 if(false&&!toMillis(UF))
 {if(!confirm(ITAPP+parms.name+ISDES+parms.frequency+CHIVL))
  {UF=parms.frequency;
   GM_setValue(S_UF,UF);
   alert(UPIN4+parms.name+REMAI+UF+UMAYS+SETTT+parms.shortname+UPIVL+
    FRMTH);
  }else
  {UF=askUF(parms,parms.frequency);
   GM_setValue(S_UF,UF);
  };
 };
 if(Number(new Date(LU))+toMillis(UF)<=new Date())
 {doUpCk();
 };
 function doUpCk()
 {LU=new Date();
  GM_setValue(S_LU,LU.toString());
  doXHRs();
 };
 function doXHRs(xhr)
 {var lyn,dte;
  GM_log('pass '+parms.i);
  if(!xhr)
  {GM_log('no response to look at - moving on');
   nextXHR(parms);
  }else
  {GM_log('response received');
   if(!xhr.status)
   {GM_log('no status found in response - moving on');
    nextXHR(parms);
   }else
   {GM_log('status code of "'+xhr.status+'" received');
    if(xhr.status!=200)
    {GM_log('error status received - moving on');
     nextXHR(parms);
    }else
    {GM_log('successful status received');
     if(!xhr.responseText)
     {GM_log('no response text received - moving on');
      nextXHR(parms);
     }else
     {GM_log(xhr.responseText.length+
       ' characters of response text received');
      lyn=xhr.responseText.
       match(/\/\/ \@released\s+([^\r\n<]+)\s*[\r\n<]/);
      if(!lyn||!lyn[1])
      {GM_log('no release date in response text - moving on');
       nextXHR(parms);
      }else
      {dte=new Date(lyn[1]);
       if(dte.toString()==INVDT)
       {GM_log('release date uninterpretable - moving on');
        nextXHR(parms);
       }else
       {GM_log('release date of "'+dte.toString()+
         '" found in response');
        GM_log('comparing to installed release - "'+
         parms.released.toString()+'"');
        if(parms.released<dte)
        {GM_log('release found is newer - '+
         'getting new release from '+parms.scriptURLs[parms.i-1]);
         if(confirm(THRIS+parms.name+GRSCR+INSIT))
         {GM_openInTab(parms.scriptURLs[parms.i-1]);
         }else
         {alert(UWILB+UF+UMAYS+SETTT+parms.shortname+UPIVL+FRMTH);
         };
         //reset for next time (if any)
         parms.i=0;
         parms.m=false;
        }else
        {GM_log('release found is not newer - ending update check');
         if(parms.m)
         {alert(THRNO+parms.name+GRSCR);
         };
         //reset for next time (if any)
         parms.i=0;
         parms.m=false;
        };//end if(parms.released<dte)
       };//end if(dte.toString()==INVDT)
      };//end if(!lyn||!lyn[1])
     };//end if(!xhr.responseText)
    };//end if(xhr.status!=200)
   };//end if(!xhr.status)
  };//end if(!xhr)
 };//end doXHRs()
 function nextXHR(pms)
 {if(pms.releaseURLs[pms.i])
  {GM_log('update check #'+(pms.i+1)+' - checking '+
    pms.releaseURLs[pms.i]);
   GM_xmlhttpRequest
   ({method:            'GET',
     url:               pms.releaseURLs[pms.i],
     headers:
     {'Cache-Control':  'no-cache',
      'Pragma':         'no-cache'
     },
     onerror:           doXHRs,
     onload:            doXHRs
    }
   );
   pms.i++;
  }else
  {GM_log('ran out of places to look for updates');
   if(confirm(SELFU+pms.name+WASUN+UF+CHIVL))
   {UF=askUF(pms,UF);
    GM_setValue(S_UF,UF);
   };
   //reset for next time (if any)
   pms.i=0;
   pms.m=false;
  };
 };

 //subroutines
 function parseParms(metaBlock)
 {var metalines=metaBlock.match(/^\/\/ \@\S+\s+.+$/gm);
  var metaparms=new ParmPack;
  var lineparts,i;
  for(i=0;i<metalines.length;i++)
  {lineparts=metalines[i].match(/^\/\/ \@(\S+)\s+(.+)$/);
   switch(lineparts[1])
   {case 'name':
    case 'shortname':
     metaparms[lineparts[1]]=lineparts[2];
     break;
    case 'released':
     metaparms[lineparts[1]]=new Date(lineparts[2]);
     break;
    case 'frequency':
     if(toMillis(lineparts[2]))
     {metaparms[lineparts[1]]=lineparts[2];
     };
     break;
    case 'releaseURL':
    case 'scriptURL':
     metaparms[lineparts[1]+'s'].push(lineparts[2]);
     break;
   };
  };
  return metaparms;
 };
 function toMillis(lyne)
 {if(!lyne)
  {return null;
  };
  var wurds=lyne.split(/\s+/);
  if(wurds.length!=2)
  {return null;
  };
  switch(wurds[1].toLowerCase())
  {case 'months':
   case 'month':
    wurds[0]*=4.4;//close enough
   case 'weeks':
   case 'week':
    wurds[0]*=7;
   case 'days':
   case 'day':
    wurds[0]*=24;
   case 'hours':
   case 'hour':
    wurds[0]*=60;
   case 'minutes':
   case 'minute':
    return wurds[0]*60*1000;
   default:
    return null;
  };
 };
 function askUF(pms,was)
 {var x=prompt(HWLNG+pms.name+WAITB,was);
  if(x==null)
  {alert(UPIN4+pms.name+REMAI+was+UMAYS+SETTT+pms.shortname+UPIVL+
    FRMTH);
   return was;
  }else
  {while(!toMillis(x))
   {x=prompt(DIDNT+HWLNG+pms.name+WAITB,was);
    if(x==null)
    {alert(UPIN4+pms.name+REMAI+was+UMAYS+SETTT+pms.shortname+UPIVL+
      FRMTH);
     return was;
    };
   };
   return x;
  };
 };

 //recreation of Greasemonkey functions
 function GM_log(str)
 {opera.postError(str);
 };
 function GM_registerMenuCommand(nm,fn)
 {//no decent equivalent found yet
 };
 function GM_getValue(nm,vl)
 {//no decent equivalent found yet
 };
 function GM_setValue(nm,vl)
 {//no decent equivalent found yet
 };
 function GM_openInTab(url)
 {//no decent equivalent found yet
 };
 function GM_xmlhttpRequest(htobj)
 {//no decent equivalent found yet
 };

 //constructor
 function ParmPack()
 {this.name=null;
  this.shortname=null;
  this.released=null;
  this.frequency=null;
  this.releaseURLs=[];
  this.scriptURLs=[];
  this.i=0;
  this.m=false;
 };
};