The version of the script here comes pre-populated with example addons (The ones that I use), download link below.
You can download this script
here #!/usr/bin/php
<?php
//============================================================================
// This script will keep wow addons up to date.
//
// You need to edit the $config['wowdir'] variable, and add the required
// "addAddon()" lines under the "Addons to keep up to date" heading.
//============================================================================
// Changes:
//======================================
// 04/01/2008 - Added Ability to store addAddon lines in a separate file
// rather than having to edit this file every time there is an
// update.
//======================================
// 24/12/2008 - Added GroupCalendar
//======================================
// 17/12/2008 - Added DoTimer
// - Added Recount
// - Added DeadlyBossMods
// - Added Ripped
//======================================
// 14/12/2008 - Added Enhanced Flight Map Official download location
// - Added EasySerum
// - Added X-DF-Date header.
//======================================
// 28/11/2008 - Fixed issue where sometimes toc files were corrupted causing
// wow to show the addon as incomatible
// - Lower the timout when trying to find the remote version
// - Dont show nasty PHP error if remote server returned nothing
//======================================
// 18/11/2008 - Added Support for "Manual" type downloads (just shows a
// message after the rest have downloaded)
// - Added Auctioneer to example list (beta and normal)
// - Added QuestHelper to example list
// - Fixes for adding X-DF-Version to stop it sometimes being added
// twice, or blank.
// - Fixed WOWUI support
// - Added force-update config option (downloadall)
//===================
// 15/10/2008 - Updated for Curse url Changes
// - Started keeping a record of changes
//============================================================================
// WoW Dir - Edit this
$config['wowdir'] = '/usr/games/wow/';
// Addon Dir (Can usually leave alone)
$config['addondir'] = $config['wowdir'].'Interface/AddOns/';
// Addons list Filename
$config['addonlist'] = $config['wowdir'].'Interface/AddonList.php';
// Download addons if remote version isn't obtainable but download url is.
$config['downloadblank'] = true;
// Force the updating of all addons.
$config['downloadall'] = false;
// Addon Types - Leave this alone
define(CUSTOM, "Custom");
define(CURSE, "Curse");
define(WOWINTERFACE, "WoW Interface");
define(COSMOS, "Cosmos");
define(MANUAL, "Manual Download"); // These are shown as reminders at the end.
// WoWUI don't provide a version number on their page which makes it
// impossible to see what the newest version is, so we guess based on last
// update date, and file size.
define(WOWUI, "WoW UI");
// Used to store the list of addons. - Leave this alone
$addons = array();
/**
* Add an Addon to the array of addons. (Called below)
*
* @param $name Name of addon
* @param $type Type of addon (CURSE etc)
* @param $id ID of addon (used for downloading, or url for manual checking)
* @param $tocname (Optional) Name of dir/toc if not the same as addon name,
* used to check current version.
* @param $custom Array used for custom information used for download.
*/
function addAddon($name, $type, $id, $tocname='', $custom=array()) {
global $addons, $config;
$addon['name'] = $name;
$addon['type'] = $type;
$addon['id'] = $id;
$addon['tocname'] = ($tocname == '') ? $name : $tocname;
$addon['dir'] = $config['addondir'].$addon['tocname'].'/';
foreach ($custom as $k => $v) {
if (!isset($addon[$k])) {
$addon[$k] = $v;
}
}
$addons[] = $addon;
}
//==========================================================================//
// Addons to keep up to date, edit these.
//==========================================================================//
if (file_exists($config['addonlist'])) {
include($config['addonlist']);
} else {
addAddon("Atlas", CURSE, "Atlas");
addAddon("AtlasLoot Enhanced", CURSE, "AtlasLoot-Enhanced", "AtlasLoot");
addAddon("Baud Bag", CURSE, "Baud-Bag", "BaudBag");
addAddon("Examiner", CURSE, "Examiner");
addAddon("FreeRefills", CURSE, "Free-Refills");
addAddon("FloAspectBar", CURSE, "Flo-Aspect-Bar");
addAddon("MobInfo-2 (includes MobHealth)", CURSE, "mob-info-2-includes-mob-health", "MobInfo2");
addAddon("MozzFullWorldMap(Fan's Update)", CURSE, "mozz-full-world-map-fans-update", "MozzFullWorldMap");
addAddon("Omen Threat Meter", CURSE, "Omen-Threat-Meter", "Omen");
//addAddon("PetInfo", CURSE, "pet-info");
addAddon("WarlockAlert", CURSE, "warlock-alert");
addAddon("QuestHelper", CURSE, "quest-helper");
addAddon("EasySerum", CURSE, "easyserum");
addAddon("DoTimer", CURSE, "do-timer");
addAddon("Recount", CURSE, "recount");
addAddon("Ripped", CURSE, "ripped");
addAddon("RippedAdvancedWarlockStrategy", CURSE, "arws");
addAddon("GroupCalendar", CURSE, "group-calendar");
addAddon("DoubleWide", WOWINTERFACE, 6887, '', array("FileVersionRegex" => "/## Title: DoubleWide \(v(.*)\)/", "AddToc" => true));
addAddon("EquipCompare", WOWINTERFACE, 4392);
addAddon("AuditorFu", WOWINTERFACE, 5429, "FuBar_AuditorFu");
addAddon("LightHeaded", WOWINTERFACE, 7017);
addAddon("TomTom", WOWINTERFACE, 7032);
addAddon("Titan Panel", WOWINTERFACE, 8092, "Titan");
addAddon("CTMod", CUSTOM, 0, "CT_Core", array("downloadurl" => "http://www.ctmod.net/media/packages/Advanced.zip", "custom_url" => "http://www.ctmod.net/downloads/", "custom_versionregex" => "/<b>This package contains:<\/b> CT_Core v(.*?),/", "FileVersionRegex" => '/^## Version: (.*) \(.*$/i'));
addAddon("FeedOMatic", CUSTOM, 0, "GFW_FeedOMatic", array("custom_url" => "http://fizzwidget.com/notes/feedomatic/feed", "custom_versionregex" => "/<title>Feed-O-Matic (.*?)<\/title>/", "custom_downloadregex" => "/<enclosure url=\"(http:\/\/fizzwidget.com\/downloads\/.*?)\" length/"));
addAddon("Quest Log Level Patch", COSMOS, "QuestLogLevelPatch", "QuestLogLevelPatch");
addAddon("Enhanced Flight Map", CUSTOM, 0, "EnhancedFlightMap", array("custom_preurl" => "http://lysaddons.game-host.org/download/", "custom_url" => "http://lysaddons.game-host.org/efm/index.html", "custom_versionregex" => "/latest version of the addon <A HREF=\"..\/download\/EnhancedFlightMap-(.*?)\.zip\">Here<\/A>/", "custom_downloadregex" => "/latest version of the addon <A HREF=\"..\/download\/(EnhancedFlightMap-.*?\.zip)\">Here<\/A>/"));
// addAddon("Auctioneer", MANUAL, "http://auctioneeraddon.com/dl/");
// addAddon("Auctioneer", CURSE, "auctioneer", "Auc-Advanced", array("FileVersionRegex" => "/## Version: (.*?) \(/"));
addAddon("Auctioneer-Beta", CUSTOM, 0, "Auc-Advanced", array("FileVersionRegex" => "/## Version: (.*?) \(/", "custom_preurl" => "http://mirror01.auctioneeraddon.com/dl/", "custom_url" => "http://auctioneeraddon.com/dl/", "custom_versionregex" => "/<td class=\"beta\"><a href=\"\?dl=Packages(?:.*?)\/AuctioneerSuite-(.*?)\.zip\">AuctioneerSuite-(?:.*?)\.zip<\/a>/", "custom_downloadregex" => "/<td class=\"beta\"><a href=\"\?dl=(Packages.*\/AuctioneerSuite-.*?\.zip)\">AuctioneerSuite-(?:.*?)\.zip<\/a>/"));
addAddon("Deadly Boss Mods", CUSTOM, 0, "DBM-Core", array("custom_preurl" => "http://www.deadlybossmods.com/", "custom_url" => "http://www.deadlybossmods.com/downloads.php", "custom_versionregex" => "/<tr><td><a href=\".*?\">DBM-Core\+WotLK Mods<\/a><\/td><td>(.*?)<\/td><td>/", "custom_downloadregex" => "/<tr><td><a href=\"(.*?)\">DBM-Core\+WotLK Mods<\/a><\/td><td>.*?<\/td><td>/"));
addAddon('Improved Casting Bar', WOWUI, 3657, "improvedCastingBar");
// addAddon("AutoBindOnPickup", CURSE, 227);
// addAddon("Notes", CURSE, 3628);
// addAddon("SuperMacro", WOWUI, 1325);
// addAddon("WeaponQuickSwap", WOWUI, 121);
}
//==========================================================================//
// Functions for actual work, no need to edit below here.
//==========================================================================//
// Used for echoing
define(CRLF, "\n");
define(T, "\t");
/**
* Check each addon, and do work etc!
*/
function checkAddons() {
global $addons, $config;
foreach ($addons as &$addon) {
if ($addon['type'] == MANUAL) { continue; }
echo 'Found addon: ', $addon['name'], CRLF;
getLocalVersion($addon);
$tries = 0;
do {
echo '.';
getRemoteData($addon);
$tries++;
} while ((!isset($addon['newversion']) || $addon['newversion'] == '') && $tries < 2);
echo T, ' Local Version: "', trim($addon['myversion']), '"', CRLF;
echo T, 'Remote Version: "', trim($addon['newversion']), '"', CRLF;
echo T, ' Download URL: "', trim($addon['downloadurl']), '"', ' [', $addon['type'], ']', CRLF;
$my = trim($addon['myversion']);
$new = trim($addon['newversion']);
if ($my != $new) {
echo T, '[Needs Update]', CRLF;
} else if ($config['downloadall']) {
echo T, '[Forced Update]', CRLF;
}
}
echo '=====================================', CRLF;
echo 'Needing Update:', CRLF;
$downloads = array();
foreach ($addons as $a) {
if ($addon['type'] == MANUAL) { continue; }
$my = trim($a['myversion']);
$new = trim($a['newversion']);
if ($new == "" && !$config['downloadblank']) {
echo T, $a['name'], ' -> Unable to find current version.', CRLF;
} else if ($a['downloadurl'] == "") {
echo T, $a['name'], ' -> Unable to find download URL.', CRLF;
} else if ($my != $new || $config['downloadall']) {
echo T, $a['name'], ' ("', $my, '" != "', $new, '") [', $a['downloadurl'], ']', CRLF;
$downloads[] = array("name" => $a['name'].'.zip', "url" => $a['downloadurl'], "version" => $new, "addon" => $a);
}
}
echo '=====================================', CRLF;
echo 'Updating:', CRLF;
// Now download them!
getAddons($downloads);
// Cleanup
execAndPrint('rm -Rfv AddOns/*.txt AddOns/*.zip*');
execAndPrint('chmod -Rf 755 '.$config['addondir']);
// Remind about manual addons.
$first = true;
foreach ($addons as &$addon) {
if ($addon['type'] == MANUAL) {
if ($first) {
echo CRLF, 'The following addons need checked manually:', CRLF;
$first = false;
}
echo T, $addon['name'], ' -> '. $addon['id'], CRLF;
}
}
}
/**
* Download/Install addons on linux.
*
* @param $downloads Array from checkAddons() with addons to get
*/
function getAddons($downloads) {
if (PHP_OS == 'Linux' || PHP_OS == 'Darwin' || substr(PHP_OS, -3) == 'BSD') {
getAddonsUnix($downloads);
} else {
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
echo 'Error: This script only updates on unix-based OSs', CRLF;
echo 'Your OS: ', PHP_OS, CRLF;
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
die();
}
}
/**
* Prepare for download/unzip by finding the applications.
* This uses "which"
*/
function prepareGet() {
global $config;
if (file_exists('/usr/bin/which') || file_exists('/bin/which')) {
$which = file_exists('/usr/bin/which') ? '/usr/bin/which' : '/bin/which';
} else {
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
echo 'Error: This script was unable to figure out how to download on your OS.', CRLF;
echo 'Your OS: ', PHP_OS, CRLF;
echo 'Unable to find `which` (in either /usr/bin or /bin)', CRLF;
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
die();
}
$wget = trim(`$which wget`);
$curl = trim(`$which curl`);
$fetch = trim(`$which fetch`);
if ($wget != '') {
$config['download_method'] = 'wget';
$config['download_app'] = $wget;
} else if ($curl != '') {
$config['download_method'] = 'curl';
$config['download_app'] = $curl;
} else if ($fetch != '') {
$config['download_method'] = 'fetch';
$config['download_app'] = $fetch;
} else {
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
echo 'Error: This script was unable to figure out how to download on your OS.', CRLF;
echo 'Your OS: ', PHP_OS, CRLF;
echo 'Supported Methods:', CRLF;
echo T, 'wget, curl, fetch', CRLF;
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
die();
}
$unzip = trim(`$which unzip`);
if ($unzip != '') {
$config['unzip_method'] = 'unzip';
$config['unzip_app'] = $unzip;
} else {
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
echo 'Error: This script was unable to figure out how to unzip on your OS.', CRLF;
echo 'Your OS: ', PHP_OS, CRLF;
echo 'Supported Methods:', CRLF;
echo T, 'unzip', CRLF;
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', CRLF;
die();
}
}
/**
* Download/Install addons on linux.
*
* @param $downloads Array from checkAddons() with addons to get
*/
function getAddonsUnix($downloads) {
global $config;
// Make sure we have the ability to download/unzip
prepareGet();
chdir($config['addondir']);
if (!file_exists($config['addondir'].'zipfiles')) {
mkdir($config['addondir'].'zipfiles');
}
foreach ($downloads as $dl) {
if ($config['download_method'] == 'wget') {
execAndPrint($config['download_app'].' "'.$dl['url'].'" -O "'.$dl['name'].'" 2>&1');
} else if ($config['download_method'] == 'curl') {
execAndPrint($config['download_app'].' "'.$dl['url'].'" -# -o "'.$dl['name'].'" 2>&1');
} else if ($config['download_method'] == 'fetch') {
execAndPrint($config['download_app'].' -o "'.$dl['name'].'" "'.$dl['url'].'" 2>&1');
}
if ($config['unzip_method'] == 'unzip') {
execAndPrint($config['unzip_app'].' -o "'.$dl['name'].'" -d "'.$config['addondir'].'" 2>&1');
}
execAndPrint('/bin/mv -fv "'.$dl['name'].'" zipfiles 2>&1');
addNewVersion($dl['addon']);
}
}
/**
* Adds the verison number we think this addon is to the toc file.
* This is used because some addons are crap and don't provide this data!
*
* We add the X-DF-Version and X-DF-Date params.
*
* @param $addon to add to!
*/
function addNewVersion($addon) {
echo "Updating Toc for ".$addon." (".$addon['tocfile'].")".CRLF;
$data = explode("\n", file_get_contents($addon['tocfile']));
foreach ($data as $line) {
if (stristr($line, 'X-DF-Version') === False) {
$newdata[] = trim($line);
}
if (stristr($line, '## Interface:') !== False) {
$newdata[] = '## X-DF-Version: '.trim($addon['newversion']);
$newdata[] = '## X-DF-Date: '.time().' ['.date('r').']';
}
}
file_put_contents($addon['tocfile'], implode("\n", $newdata));
}
/**
* Execute the given command and print the output from stdout as it happens
*
* @param $cmd Command to execute
*/
function execAndPrint($cmd) {
echo "execAndPrint: ", $cmd, "\n";
appexec($cmd, $proc);
fclose($proc['pipes'][0]);
// Now close the process handle
while (!feof($proc['pipes'][1])) {
echo fread($proc['pipes'][1], 512);
flush();
}
fclose($proc['pipes'][1]);
proc_close($proc['process']);
}
/**
* Runs an application and puts the process and the pipes in the given array
* $process['pipes'][0] is stdin
* $process['pipes'][1] is stdout
*
* @param $path Patch to exec
* @param $process Array where process pipes will be put
* @return true/false on successful execution
*/
function appexec($path, &$process) {
$descriptorspec = array(0 => array("pipe", "r"),1 => array("pipe", "w"),2 => array("file", "/dev/null", "a"));
$process = array();
$process['process'] = proc_open($path, $descriptorspec, $process['pipes']);
return is_resource($process['process']);
}
/**
* Get the local version of the given addon.
* This sets $addon['myversion'].
* -3 = Local copy of Addon not found
* -2 = TOC not found
* -1 = Version not found.
*
* @param $addon Array with addon information
*/
function getLocalVersion(&$addon) {
unset($addon['myversion']);
unset($addon['toc']);
unset($addon['dfversion']);
if (!file_exists($addon['dir'])) {
$addon['myversion'] = -3;
return;
}
$toc = $addon['dir'].$addon['tocname'].'.toc';
if (!file_exists($toc)) {
// Look for toc, it could have a different case -_-
$found = false;
if ($handle = opendir($addon['dir'])) {
while (false !== ($file = readdir($handle))) {
if (strtolower($file) == strtolower($addon['tocname'].'.toc')) {
$toc = $addon['dir'].$file;
$found = true;
break;
}
}
if (!$found) { $addon['myversion'] = -2; return; }
}
}
$addon['tocfile'] = $toc;
$lines = file($toc);
foreach ($lines as $line) {
$regex = (isset($addon['FileVersionRegex'])) ? $addon['FileVersionRegex'] : '/^## Version: (.*)$/i';
$dfversion = '/^## X-DF-Version: (.*)$/i';
$tocregex = '/^## Interface: (.*)$/i';
if (!isset($addon['myversion']) && preg_match($regex, $line, $matches)) {
$addon['myversion'] = $matches[1];
} else if (!isset($addon['dfversion']) && preg_match($dfversion, $line, $matches)) {
$addon['dfversion'] = $matches[1];
} else if (!isset($addon['toc']) && preg_match($tocregex, $line, $matches)) {
$addon['toc'] = $matches[1];
}
}
if (isset($addon['myversion']) || isset($addon['dfversion'])) {
if (isset($addon['AddToc'])) {
$addon['myversion'] .= '-'.$addon['toc'];
}
if (isset($addon['dfversion'])) { $addon['myversion'] = $addon['dfversion']; }
return $addon['myversion'];
}
$addon['myversion'] -1;
}
/**
* Get the remote version/download url of the given addon
* This populates $addon['newversion'] and $addon['downloadurl']
*
* @param $addon Array with addon information
*/
function getRemoteData(&$addon) {
Switch ($addon['type']) {
case CURSE:
$url = 'http://wow.curse.com/downloads/wow-addons/details/'.$addon['id'].'.aspx';
$preurl = 'http://wow.curse.com';
$mid = (isset($addon['VersionSubRegex'])) ? $addon['VersionSubRegex'] : '(?:'.preg_quote($addon['name']).' )?r?v?(?:ersion)?(.*?)' ;
$versionregex = '/<a href="\/downloads\/wow-addons\/details\/.*?\/download\/[0-9]+.aspx">'.$mid.'<\/a>/i';
$downloadregex = '/<a title=".*?" class="button button-download" href="(\/downloads\/wow-addons\/details\/.*?\/download\/[0-9]+.aspx)"/i';
break;
case WOWINTERFACE:
$url = 'http://www.wowinterface.com/downloads/info'.$addon['id'].'.html';
$preurl = 'http://www.wowinterface.com';
$versionregex = '/<td class="alt2"><div class="infoboxfont1">Version:<\/div><\/td>.+?<td class="alt2"><div class="smallfont">r?v?(?:ersion)?(.*?)<\/div><\/td>/ims';
$addon['downloadurl'] = 'http://www.wowinterface.com/downloads/download'.$addon['id'];
break;
case WOWUI:
$url = 'http://wowui.worldofwar.net/?p=mod&m='.$addon['id'];
$addon['downloadurl'] = 'http://wowui.worldofwar.net/?p=download&m='.$addon['id'];
$versionregex = '/<b>Download size:<\/b> (.*?)<br \/>Updated <b>(.*?)<\/b>/i';
break;
case COSMOS:
$url = 'http://www.cosmosui.org/addons.php?info='.$addon['id'];
$preurl = 'http://www.cosmosui.org/';
$versionregex = '/<li>Revision: (.*)<\/li>/i';
$downloadregex = '/href="(download\.php\?t=addons&f=.*?)"/i';
break;
case CUSTOM:
$url = $addon['custom_url'];
if (isset($addon['custom_preurl'])) { $preurl = $addon['custom_preurl']; }
if (isset($addon['custom_versionregex'])) { $versionregex = $addon['custom_versionregex']; }
if (isset($addon['custom_downloadregex'])) { $downloadregex = $addon['custom_downloadregex']; }
break;
default:
return -1;
break;
}
$context = stream_context_create(array('http' => array('timeout' => 5)));
$page = @file_get_contents($url, false, $context);
if ($page === false) {
$addon['newversion'] = "Unknown";
$addon['downloadurl'] = "";
return;
}
if (isset($downloadregex)) {
preg_match($downloadregex, $page, $matches);
$addon['downloadurl'] = $matches[1];
if (isset($preurl)) { $addon['downloadurl'] = $preurl.$addon['downloadurl']; }
Switch ($addon['type']) {
case CURSE:
$page2 = file_get_contents($addon['downloadurl']);
$regex = '/<a class="button button-pop" href="(.*?)"><span>Manual Install<\/span>/';
preg_match($regex, $page2, $matches);
$addon['downloadurl'] = html_entity_decode($matches[1]);
if (!empty($addon['downloadurl'])) {
$addon['downloadurl'] = $preurl.$addon['downloadurl'];
}
break;
}
}
preg_match($versionregex, $page, $matches);
Switch ($addon['type']) {
case WOWUI:
$addon['newversion'] = preg_replace('/\s/', '', $matches[1].$matches[2]);
break;
default:
$addon['newversion'] = $matches[1];
}
}
// And begin!
checkAddons();
?>