Merge branch 'master' into highlighter

This commit is contained in:
vita10gy
2013-04-30 22:58:09 -05:00
52 changed files with 4226 additions and 532 deletions

View File

@ -1,9 +1,9 @@
So far, these people have contributed:
--------------------------------------
[Bananeweizen](https://github.com/Bananeweizen),
[blakjakau](https://github.com/blakjakau),
[boombuler](https://github.com/boombuler),
[breunigs](https://github.com/breunigs),
[ccjon](https://github.com/ccjon),
[cmrn](https://github.com/cmrn),
[epf](https://github.com/epf),

View File

@ -93,7 +93,7 @@ def loaderMD(var):
payload = {'text': file, 'mode': 'markdown'}
headers = {'Content-Type': 'application/json'}
req = urllib2.Request(url, json.dumps(payload).encode('utf8'), headers)
md = urllib2.urlopen(req).read().decode('utf8').replace('\n', '').replace('\'', '\\\'')
md = urllib2.urlopen(req).read().decode('utf8').replace('\n', '\\n').replace('\'', '\\\'')
files[fn] = {}
files[fn][filemd5] = md
db['files'] = files
@ -205,12 +205,24 @@ if buildMobile:
if buildMobile not in ['debug','release']:
raise Exception("Error: buildMobile must be 'debug' or 'release'")
# first, copy the IITC script into the mobile folder. create the folder if needed
# compile the user location script
fn = "user-location.user.js"
script = readfile("mobile/" + fn)
downloadUrl = distUrlBase and distUrlBase + '/' + fn.replace("\\","/") or 'none'
updateUrl = distUrlBase and downloadUrl.replace('.user.js', '.meta.js') or 'none'
script = doReplacements(script, downloadUrl=downloadUrl, updateUrl=updateUrl)
metafn = fn.replace('.user.js', '.meta.js')
saveScriptAndMeta(script, os.path.join(outDir,fn), os.path.join(outDir,metafn))
# copy the IITC script into the mobile folder. create the folder if needed
try:
os.makedirs("mobile/assets")
except:
pass
shutil.copy(os.path.join(outDir,"total-conversion-build.user.js"), "mobile/assets/iitc.js")
# copy the user location script into the mobile folder.
shutil.copy(os.path.join(outDir,"user-location.user.js"), "mobile/assets/user-location.user.js")
# also copy plugins
try:

View File

@ -83,6 +83,26 @@ window.setupLayerChooserSelectOne = function() {
});
}
// Setup the function to record the on/off status of overlay layerGroups
window.setupLayerChooserStatusRecorder = function() {
// Record already added layerGroups
$.each(window.layerChooser._layers, function(ind, chooserEntry) {
if(!chooserEntry.overlay) return true;
var display = window.map.hasLayer(chooserEntry.layer);
window.updateDisplayedLayerGroup(chooserEntry.name, display);
});
// Record layerGroups change
window.map.on('layeradd layerremove', function(e) {
var id = L.stamp(e.layer);
var layerGroup = this._layers[id];
if (layerGroup && layerGroup.overlay) {
var display = (e.type === 'layeradd');
window.updateDisplayedLayerGroup(layerGroup.name, display);
}
}, window.layerChooser);
}
window.setupStyles = function() {
$('head').append('<style>' +
[ '#largepreview.enl img { border:2px solid '+COLORS[TEAM_ENL]+'; } ',
@ -206,14 +226,16 @@ window.setupMap = function() {
// update map hooks
map.on('movestart zoomstart', window.requests.abort);
map.on('moveend zoomend', function() { window.startRefreshTimeout(500) });
// run once on init
window.requestData();
window.startRefreshTimeout();
map.on('moveend zoomend', function() { console.log('map moveend'); window.startRefreshTimeout(ON_MOVE_REFRESH*1000) });
window.addResumeFunction(window.requestData);
window.requests.addRefreshFunction(window.requestData);
// start the refresh process with a small timeout, so the first data request happens quickly
// (the code originally called the request function directly, and triggered a normal delay for the nxt refresh.
// however, the moveend/zoomend gets triggered on map load, causing a duplicate refresh. this helps prevent that
window.startRefreshTimeout(ON_MOVE_REFRESH*1000);
};
// renders player details into the website. Since the player info is
@ -370,6 +392,7 @@ function boot() {
window.chat.setup();
window.setupQRLoadLib();
window.setupLayerChooserSelectOne();
window.setupLayerChooserStatusRecorder();
window.setupBackButton();
// read here ONCE, so the URL is only evaluated one time after the
// necessary data has been loaded.

View File

@ -1,96 +1,227 @@
// REDEEMING /////////////////////////////////////////////////////////
/* Resource type names mapped to actual names and abbreviations.
* Add more here if necessary.
*/
window.REDEEM_RESOURCES = {
RES_SHIELD: {long: 'Portal Shield', short: 'SH'},
EMITTER_A: {long: 'Resonator', short: 'R'},
EMP_BURSTER: {long: 'XMP Burster', short: 'X'},
POWER_CUBE: {long: 'Power Cube', short: 'C'}
};
/* Redemption errors. Very self-explanatory.
*/
window.REDEEM_ERRORS = {
ALREADY_REDEEMED: 'The passcode has already been redeemed.',
ALREADY_REDEEMED_BY_PLAYER : 'You have already redeemed this passcode.',
INVALID_PASSCODE: 'This passcode is invalid.'
};
/* These are HTTP status codes returned by the redemption API.
* TODO: Move to another file? Use more generally across IITC?
*/
window.REDEEM_STATUSES = {
429: 'You have been rate-limited by the server. Wait a bit and try again.',
500: 'Internal server error'
};
/* Encouragement for people who got it in.
* Just for fun.
*/
window.REDEEM_ENCOURAGEMENT = [
"Passcode accepted!",
"Access granted.",
"Asset transfer in progress.",
"Well done, Agent.",
"Make the " + {'RESISTANCE' : 'Resistance', 'ALIENS' : 'Enlightened'}[PLAYER.team] + " proud!"
];
/* Redemption "handlers" handle decoding and formatting for rewards.
*
* Redemption "decoders" are used for returning the primary attribute (key) from
* different types of items. Pretty self-explanatory.
*
* Redemption "formatters" are used for formatting specific types of password rewards.
* Right now, Ingress has resourceWithLevels (leveled resources) and modResource (mods).
* Resources with levels have levels, and mods have rarity. Format them appropriately.
*/
window.REDEEM_HANDLERS = {
'resourceWithLevels' : {
decode: function(type, resource) {return resource.level;},
format: function(acquired, level) {
var prefix = '<span style="color: ' + (window.COLORS_LVL[level] || 'white') + ';">';
var suffix = '</span>';
return {
table: '<td>' + prefix + 'L' + level + suffix + '</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
html: acquired.count + '&#215;' + acquired.name.short + prefix + level + suffix,
plain: acquired.count + '@' + acquired.name.short + level
};
}
},
'modResource' : {
decode: function(type, resource) {return resource.rarity;},
format: function(acquired, rarity) {
var prefix = '<span style="color: ' + (window.COLORS_MOD[rarity] || 'white') + ';">';
var suffix = '</span>';
var abbreviation = rarity.split('_').map(function (i) {return i[0];}).join('');
return {
table: '<td>' + prefix + abbreviation + suffix + '</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
html: acquired.count + '&#215;' + prefix + abbreviation + suffix,
plain: acquired.count + '@' + abbreviation
};
}
},
'default' : {
decode: function(type, resource) {return 'UNKNOWN';},
format: function(acquired, group) {
return {
table: '<td>+</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
html: acquired.count + '&#215;' + acquired.name.short,
plain: acquired.count + '@' + acquired.name.short
};
}
}
};
/* Redemption "hints" hint at what an unknown resource might be from its object properties.
*/
window.REDEEM_HINTS = {
level: 'resourceWithLevels',
rarity: 'modResource'
};
window.handleRedeemResponse = function(data, textStatus, jqXHR) {
var passcode = this.passcode, to_alert, to_log;
if(data.error) {
var error = '';
if(data.error === 'ALREADY_REDEEMED') {
error = 'The passcode has already been redeemed.';
} else if(data.error === 'ALREADY_REDEEMED_BY_PLAYER') {
error = 'You have already redeemed this passcode.';
} else if(data.error === 'INVALID_PASSCODE') {
error = 'This passcode is invalid.';
} else {
error = 'There was a problem redeeming the passcode. Try again?';
}
alert('<strong>' + data.error + '</strong>\n' + error);
to_alert = '<strong>' + data.error + '</strong><br />' + (window.REDEEM_ERRORS[data.error] || 'There was a problem redeeming the passcode. Try again?');
to_log = '[ERROR] ' + data.error;
} else if(data.result) {
var tblResult = $('<table class="redeem-result" />');
tblResult.append($('<tr><th colspan="2">Passcode accepted!</th></tr>'));
var encouragement = window.REDEEM_ENCOURAGEMENT[Math.floor(Math.random() * window.REDEEM_ENCOURAGEMENT.length)];
var payload = {};
var inferred = [];
var results = {
'table' : ['<th colspan="2" style="text-align: left;"><strong>' + encouragement + '</strong></th>'],
'html' : [],
'plain' : []
};
if(data.result.apAward)
tblResult.append($('<tr><td>+</td><td>' + data.result.apAward + 'AP</td></tr>'));
if(data.result.xmAward)
tblResult.append($('<tr><td>+</td><td>' + data.result.xmAward + 'XM</td></tr>'));
// Track frequencies and levels of items
$.each(data.result.inventoryAward, function (award_idx, award) {
var acquired = award[2], handler, type, key, name;
var resonators = {};
var bursts = {};
var shields = {};
var cubes = {};
// The "what the heck is this item" heuristic
$.each(acquired, function (taxonomy, resource) {
if('resourceType' in resource) {
if(taxonomy in window.REDEEM_HANDLERS) {
// Cool. We know how to directly handle this item.
handler = {
functions: window.REDEEM_HANDLERS[taxonomy],
taxonomy: taxonomy,
processed_as: taxonomy
};
} else {
// Let's see if we can get a hint for how we should handle this.
$.each(resource, function (resource_key, resource_value) {
if(resource_key in window.REDEEM_HINTS) {
// We're not sure what this item is, but we can process it like another item
handler = {
functions: (window.REDEEM_HANDLERS[window.REDEEM_HINTS[resource_key]] || window.REDEEM_HANDLERS['default']),
taxonomy: taxonomy,
processed_as: window.REDEEM_HINTS[resource_key]
};
return false;
}
});
for(var i in data.result.inventoryAward) {
var acquired = data.result.inventoryAward[i][2];
if(acquired.modResource) {
if(acquired.modResource.resourceType === 'RES_SHIELD') {
var rarity = acquired.modResource.rarity.split('_').map(function (i) {return i[0]}).join('');
if(!shields[rarity]) shields[rarity] = 0;
shields[rarity] += 1;
}
} else if(acquired.resourceWithLevels) {
if(acquired.resourceWithLevels.resourceType === 'EMITTER_A') {
var level = acquired.resourceWithLevels.level
if(!resonators[level]) resonators[level] = 0;
resonators[level] += 1;
} else if(acquired.resourceWithLevels.resourceType === 'EMP_BURSTER') {
var level = acquired.resourceWithLevels.level
if(!bursts[level]) bursts[level] = 0;
bursts[level] += 1;
} else if(acquired.resourceWithLevels.resourceType === 'POWER_CUBE') {
var level = acquired.resourceWithLevels.level
if(!cubes[level]) cubes[level] = 0;
cubes[level] += 1;
}
}
// Fall back to the default handler if necessary
handler = handler || {
functions: window.REDEEM_HANDLERS['default'],
taxonomy: taxonomy,
processed_as: 'default'
};
}
$.each(resonators, function(lvl, count) {
var text = 'Resonator';
if(count >= 2) text += ' ('+count+')';
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
});
$.each(bursts, function(lvl, count) {
var text = 'Xmp Burster';
if(count >= 2) text += ' ('+count+')';
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
});
$.each(cubes, function(lvl, count) {
var text = 'Power Cube';
if(count >= 2) text += ' ('+count+')';
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
});
$.each(shields, function(lvl, count) {
var text = 'Portal Shield';
if(count >= 2) text += ' ('+count+')';
tblResult.append($('<tr><td>'+lvl+'</td><td>'+text+'</td></tr>'));
});
alert(tblResult, true);
// Collect the data that we know
type = resource.resourceType;
key = handler.functions.decode(type, resource);
name = window.REDEEM_RESOURCES[type] || {long: type, short: type[0]};
// Decide if we inferred this resource
if(!(type in window.REDEEM_RESOURCES) || handler.taxonomy !== handler.processed_as) {
name.long += '*';
name.short += '*';
inferred.push({type: type, key: key, handler: handler});
}
return false;
}
});
// Update frequencies
payload[type] = payload[type] || {};
payload[type][key] = payload[type][key] || {};
payload[type][key].handler = payload[type][key].handler || handler;
payload[type][key].type = payload[type][key].type || type;
payload[type][key].name = payload[type][key].name || name;
payload[type][key].count = payload[type][key].count || 0;
payload[type][key].count += 1;
});
// Get AP and XM.
$.each([{label: 'AP', award: parseInt(data.result.apAward)}, {label: 'XM', award: parseInt(data.result.xmAward)}], function(idx, val) {
if(val.award > 0) {
results.table.push('<td>+</td><td>' + digits(val.award) + ' ' + val.label + '</td>');
results.html.push(val.award + ' ' + val.label);
results.plain.push(val.award + ' ' + val.label);
}
});
// Build the formatted results alphabetically
$.each(Object.keys(payload).sort(), function(type_idx, type) {
$.each(Object.keys(payload[type]).sort(), function(key_idx, key) {
var acquired = payload[type][key];
$.each(acquired.handler.functions.format(acquired, key), function(format, string) {
results[format].push(string);
});
});
});
// Let the user know if we had to guess
if (inferred.length > 0) {
results.table.push('<td style="font-family: monospace;">*</td><td style="font-family: monospace;">Guessed (check console)</td>');
$.each(inferred, function (idx, val) {
console.log(passcode +
' => [INFERRED] ' + val.type + ':' + val.key + ' :: ' +
val.handler.taxonomy + ' =~ ' + val.handler.processed_as);
});
}
// Add table footers
results.table.push('<td style="font-family: monospace;">&gt;</td><td><a href="javascript:alert(\'' +
escape('<span style="font-family: monospace;"><strong>' + encouragement + '</strong><br />' + results.html.join('/') + '</span>') +
'\', true);" style="font-family: monospace;">[plaintext]</a>');
// Display formatted versions in a table, plaintext, and the console log
to_alert = '<table class="redeem-result">' + results.table.map(function(a) {return '<tr>' + a + '</tr>';}).join("\n") + '</table>';
to_log = '[SUCCESS] ' + results.plain.join('/');
}
alert(to_alert, true);
console.log(passcode + ' => ' + to_log);
}
window.setupRedeem = function() {
$("#redeem").keypress(function(e) {
if((e.keyCode ? e.keyCode : e.which) != 13) return;
if((e.keyCode ? e.keyCode : e.which) !== 13) return;
var data = {passcode: $(this).val()};
window.postAjax('redeemReward', data, window.handleRedeemResponse,
function(response) {
var extra = '';
if(response && response.status) {
if(response.status === 429) {
extra = 'You have been rate-limited by the server. Wait a bit and try again.';
} else {
extra = 'The server indicated an error.';
}
extra += '\nResponse: HTTP <a href="http://httpstatus.es/' + response.status + '" alt="HTTP ' + response.status + '">' + response.status + '</a>.';
if(response.status) {
extra = (window.REDEEM_STATUSES[response.status] || 'The server indicated an error.') + ' (HTTP ' + response.status + ')';
} else {
extra = 'No status code was returned.';
}

View File

@ -8,6 +8,10 @@ window.failedRequestCount = 0;
window.requests = function() {}
//time of last refresh
window.requests._lastRefreshTime = 0;
window.requests._quickRefreshPending = false;
window.requests.add = function(ajax) {
window.activeRequests.push(ajax);
renderUpdateStatus();
@ -38,17 +42,19 @@ window.renderUpdateStatus = function() {
if(mapRunsUserAction)
t += 'paused during interaction';
else if(isIdle())
t += '<span style="color:red">Idle, not updating.</span>';
t += '<span style="color:#888">Idle, not updating.</span>';
else if(window.activeRequests.length > 0)
t += window.activeRequests.length + ' requests running.';
else if(window.requests._quickRefreshPending)
t += 'refreshing...';
else
t += 'Up to date.';
if(renderLimitReached())
t += ' <span style="color:red" class="help" title="Can only render so much before it gets unbearably slow. Not all entities are shown. Zoom in or increase the limit (search for MAX_DRAWN_*).">RENDER LIMIT</span> '
t += ' <span style="color:#f66" class="help" title="Can only render so much before it gets unbearably slow. Not all entities are shown. Zoom in or increase the limit (search for MAX_DRAWN_*).">RENDER LIMIT</span> '
if(window.failedRequestCount > 0)
t += ' <span style="color:red">' + window.failedRequestCount + ' failed</span>.'
t += ' <span style="color:#f66">' + window.failedRequestCount + ' failed</span>.'
t += '<br/>(';
var minlvl = getMinPortalLevel();
@ -78,19 +84,28 @@ window.startRefreshTimeout = function(override) {
if(refreshTimeout) clearTimeout(refreshTimeout);
var t = 0;
if(override) {
window.requests._quickRefreshPending = true;
t = override;
//ensure override can't cause too fast a refresh if repeatedly used (e.g. lots of scrolling/zooming)
timeSinceLastRefresh = new Date().getTime()-window.requests._lastRefreshTime;
if(timeSinceLastRefresh < 0) timeSinceLastRefresh = 0; //in case of clock adjustments
if(timeSinceLastRefresh < MINIMUM_OVERRIDE_REFRESH*1000)
t = (MINIMUM_OVERRIDE_REFRESH*1000-timeSinceLastRefresh);
} else {
window.requests._quickRefreshPending = false;
t = REFRESH*1000;
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
if(adj > 0) t += adj*1000;
}
var next = new Date(new Date().getTime() + t).toLocaleTimeString();
console.log('planned refresh: ' + next);
console.log('planned refresh in ' + (t/1000) + ' seconds, at ' + next);
refreshTimeout = setTimeout(window.requests._callOnRefreshFunctions, t);
renderUpdateStatus();
}
window.requests._onRefreshFunctions = [];
window.requests._callOnRefreshFunctions = function() {
console.log('running refresh at ' + new Date().toLocaleTimeString());
startRefreshTimeout();
if(isIdle()) {
@ -101,6 +116,9 @@ window.requests._callOnRefreshFunctions = function() {
console.log('refreshing');
//store the timestamp of this refresh
window.requests._lastRefreshTime = new Date().getTime();
$.each(window.requests._onRefreshFunctions, function(ind, f) {
f();
});

View File

@ -3,6 +3,7 @@
window.aboutIITC = function(){
var v = '@@BUILDNAME@@-@@BUILDDATE@@';
var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
var a = ''
+ ' <div><b>About IITC</b></div> '
+ ' <div>Ingress Intel Total Conversion</div> '
@ -23,7 +24,9 @@ window.aboutIITC = function(){
+ ' <hr>'
+ ' <div>Version: ' + v + '</div>'
+ ' <hr>'
+ ' <div>' + attrib + '</div>';
+ ' <div>' + attrib + '</div>'
+ ' <hr>'
+ ' <div>' + contrib + '</div>';
alert(a, true, function() {$('.ui-dialog').removeClass('ui-dialog-aboutIITC');});
$('.ui-dialog').addClass('ui-dialog-aboutIITC');
}
@ -82,13 +85,14 @@ window.digits = function(d) {
// able arguments: http://api.jquery.com/jQuery.ajax/
// error: see above. Additionally it is logged if the request failed.
window.postAjax = function(action, data, success, error) {
data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
var post_data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
var result = $.ajax({
url: '/rpc/dashboard.'+action,
type: 'POST',
data: data,
data: post_data,
context: data,
dataType: 'json',
success: [remove, success],
error: error ? [errCnt, error] : errCnt,
@ -346,3 +350,25 @@ window.convertTextToTableMagic = function(text) {
window.calcTriArea = function(p) {
return Math.abs((p[0].lat*(p[1].lng-p[2].lng)+p[1].lat*(p[2].lng-p[0].lng)+p[2].lat*(p[0].lng-p[1].lng))/2);
}
// Update layerGroups display status to window.overlayStatus and cookie 'ingress.intelmap.layergroupdisplayed'
window.updateDisplayedLayerGroup = function(name, display) {
overlayStatus[name] = display;
writeCookie('ingress.intelmap.layergroupdisplayed', JSON.stringify(overlayStatus));
}
// Read layerGroup status from window.overlayStatus if it was added to map,
// read from cookie if it has not added to map yet.
// return true if both overlayStatus and cookie didn't have the record
window.isLayerGroupDisplayed = function(name) {
if(typeof(overlayStatus[name]) !== 'undefined') return overlayStatus[name];
var layersJSON = readCookie('ingress.intelmap.layergroupdisplayed');
if(!layersJSON) return true;
var layers = JSON.parse(layersJSON);
// keep latest overlayStatus
overlayStatus = $.extend(layers, overlayStatus);
if(typeof(overlayStatus[name]) === 'undefined') return true;
return overlayStatus[name];
}

File diff suppressed because it is too large Load Diff

10
main.js
View File

@ -1,7 +1,7 @@
// ==UserScript==
// @id ingress-intel-total-conversion@jonatkins
// @name IITC: Ingress intel map total conversion
// @version 0.11.2.@@DATETIMEVERSION@@
// @version 0.11.3.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@ -112,8 +112,10 @@ function wrapper() {
L_PREFER_CANVAS = false;
// CONFIG OPTIONS ////////////////////////////////////////////////////
window.REFRESH = 30; // refresh view every 30s (base time)
window.REFRESH = 60; // refresh view every 60s (base time)
window.ZOOM_LEVEL_ADJ = 5; // add 5 seconds per zoom level
window.ON_MOVE_REFRESH = 0.8; //refresh time to use after a movement event
window.MINIMUM_OVERRIDE_REFRESH = 5; //limit on refresh time since previous refresh, limiting repeated move refresh rate
window.REFRESH_GAME_SCORE = 5*60; // refresh game score every 5 minutes
window.MAX_IDLE_TIME = 4; // stop updating map after 4min idling
window.PRECACHE_PLAYER_NAMES_ZOOM = 17; // zoom level to start pre-resolving player names
@ -232,6 +234,10 @@ window.links = {};
window.fields = {};
window.resonators = {};
// contain current status(on/off) of overlay layerGroups.
// But you should use isLayerGroupDisplayed(name) to check the status
window.overlayStatus = {};
// plugin framework. Plugins may load earlier than iitc, so dont
// overwrite data
if(typeof window.plugin !== 'function') window.plugin = function() {};

1
mobile/.gitignore vendored
View File

@ -7,4 +7,5 @@ libs/
proguard-project.txt
local.properties
assets/iitc.js
assets/user-location.user.js
assets/plugins/

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cradle.iitc_mobile"
android:versionCode="12"
android:versionName="0.3.1" >
android:versionCode="13"
android:versionName="0.3.2" >
<uses-sdk
android:minSdkVersion="14"
@ -14,7 +14,7 @@
<application
android:allowBackup="true"
android:icon="@drawable/iitc_icon"
android:icon="@drawable/ic_iitcm"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -18,7 +18,7 @@
<string name="pref_about_msg">
<![CDATA[<big><b>Ingress Intel Total Conversion Mobile</b></big><br><br>
<b>by <a href="https://github.com/leCradle">cradle</a></b><br><br>
<b>Icon by <a href="https://twitter.com/machtwerk">machtwerk</a></b><br><br>
<b>Icon by <a href="http://www.ludolab.net">Giuseppe Lucido</a></b><br><br>
IITC Mobile is an optimized mobile browser for the
Ingress Intel Total Conversion (IITC) userscript by <a href="https://github.com/breunigs"><b>breunigs</b></a>.
After Niantic asked the main developer to shut down his github project, development
@ -36,6 +36,8 @@
<string name="pref_plugins_title">Available plugins</string>
<string name="pref_force_desktop">Force desktop mode</string>
<string name="pref_force_desktop_sum">Nice for tablets, looks awful on smartphones</string>
<string name="pref_user_loc">Display user location</string>
<string name="pref_user_loc_sum">Show users position on map</string>
<string name="pref_developer_options">Developer options</string>
<string name="pref_select_iitc">IITC source</string>

View File

@ -17,6 +17,11 @@
android:title="@string/pref_force_desktop"
android:summary="@string/pref_force_desktop_sum"
android:defaultValue="false" />
<CheckBoxPreference
android:key="pref_user_loc"
android:title="@string/pref_user_loc"
android:summary="@string/pref_user_loc_sum"
android:defaultValue="false" />
<MultiSelectListPreference
android:key="pref_plugins"
android:title="@string/pref_plugins"

View File

@ -4,6 +4,9 @@ import java.io.IOException;
import com.cradle.iitc_mobile.R;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@ -20,6 +23,7 @@ import android.content.res.Configuration;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.Toast;
public class IITC_Mobile extends Activity {
@ -29,6 +33,10 @@ public class IITC_Mobile extends Activity {
private boolean desktop = false;
private OnSharedPreferenceChangeListener listener;
private String intel_url = "https://www.ingress.com/intel";
private boolean user_loc = false;
private LocationManager loc_mngr = null;
private LocationListener loc_listener = null;
private boolean keyboad_open = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -45,11 +53,53 @@ public class IITC_Mobile extends Activity {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("pref_force_desktop"))
desktop = sharedPreferences.getBoolean("pref_force_desktop", false);
if (key.equals("pref_user_loc")) {
user_loc = sharedPreferences.getBoolean("pref_user_loc", false);
}
IITC_Mobile.this.loadUrl(intel_url);
}
};
sharedPref.registerOnSharedPreferenceChangeListener(listener);
// we need this one to prevent location updates to javascript when keyboard is open
// it closes on updates
iitc_view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if ((iitc_view.getRootView().getHeight() - iitc_view.getHeight()) >
iitc_view.getRootView().getHeight()/3) {
Log.d("iitcm", "Open Keyboard...");
IITC_Mobile.this.keyboad_open = true;
} else {
Log.d("iitcm", "Close Keyboard...");
IITC_Mobile.this.keyboad_open = false;
}
}
});
// Acquire a reference to the system Location Manager
loc_mngr = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// Define a listener that responds to location updates
loc_listener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the network location provider.
drawMarker(location);
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
user_loc = sharedPref.getBoolean("pref_user_loc", false);
if (user_loc == true) {
// Register the listener with the Location Manager to receive location updates
loc_mngr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, loc_listener);
loc_mngr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, loc_listener);
}
// load new iitc web view with ingress intel page
Intent intent = getIntent();
String action = intent.getAction();
@ -78,6 +128,12 @@ public class IITC_Mobile extends Activity {
Log.d("iitcm", "resuming...setting reset idleTimer");
iitc_view.loadUrl("javascript: window.idleTime = 0");
iitc_view.loadUrl("javascript: window.renderUpdateStatus()");
if (user_loc == true) {
// Register the listener with the Location Manager to receive location updates
loc_mngr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, loc_listener);
loc_mngr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, loc_listener);
}
}
@Override
@ -107,6 +163,10 @@ public class IITC_Mobile extends Activity {
}
}
Log.d("iitcm", "stopping iitcm");
if (user_loc == true)
loc_mngr.removeUpdates(loc_listener);
super.onStop();
}
@ -185,6 +245,7 @@ public class IITC_Mobile extends Activity {
}
}
// vp=f enables desktop mode...vp=m is the defaul mobile view
private String addUrlParam(String url) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
this.desktop = sharedPref.getBoolean("pref_force_desktop", false);
@ -195,6 +256,8 @@ public class IITC_Mobile extends Activity {
return (url + "?vp=m");
}
// inject the iitc-script and load the intel url
// plugins are injected onPageFinished
public void loadUrl(String url) {
url = addUrlParam(url);
Log.d("iitcm", "injecting js...");
@ -202,4 +265,17 @@ public class IITC_Mobile extends Activity {
Log.d("iitcm", "loading url: " + url);
iitc_view.loadUrl(url);
}
// update the user location marker on the map
public void drawMarker(Location loc) {
// throw away all positions with accuracy > 100 meters
// should avoid gps glitches
if (loc.getAccuracy() < 100) {
if (keyboad_open == false) {
iitc_view.loadUrl("javascript: " +
"window.plugin.userLocation.updateLocation( " +
loc.getLatitude() + ", " + loc.getLongitude() + ");");
}
}
}
}

View File

@ -124,6 +124,25 @@ public class IITC_WebViewClient extends WebViewClient {
}
}
}
// inject the user location script if enabled in settings
if (sharedPref.getBoolean("pref_user_loc", false))
enableTracking(view);
}
public void enableTracking(WebView view) {
Log.d("iitcm", "enable tracking...");
AssetManager am = context.getAssets();
Scanner s = null;
String src = "";
try {
s = new Scanner(am.open("user-location.user.js")).useDelimiter("\\A");
} catch (IOException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
if (s != null) src = s.hasNext() ? s.next() : "";
view.loadUrl("javascript:" + src);
}
// Check every external resource if its okay to load it and maybe replace it
@ -153,7 +172,7 @@ public class IITC_WebViewClient extends WebViewClient {
// start non-ingress-intel-urls in another app...
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("ingress.com")) {
if (url.contains("ingress.com") || url.contains("appengine.google.com")) {
// reload iitc if a poslink is clicked inside the app
if (url.contains("intel?ll=") || (url.contains("latE6") && url.contains("lngE6"))) {
Log.d("iitcm", "should be an internal clicked position link...reload script for: " + url);

View File

@ -0,0 +1,69 @@
// ==UserScript==
// @id iitc-plugin-user-location@cradle
// @name IITC plugin: User Location
// @version 0.1.0.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Show user location marker on map
// @include https://www.ingress.com/intel*
// @include http://www.ingress.com/intel*
// @match https://www.ingress.com/intel*
// @match http://www.ingress.com/intel*
// ==/UserScript==
function wrapper() {
// ensure plugin framework is there, even if iitc is not yet loaded
if(typeof window.plugin !== 'function') window.plugin = function() {};
// PLUGIN START ////////////////////////////////////////////////////////
window.plugin.userLocation = function() {};
window.plugin.userLocation.marker = {};
window.plugin.userLocation.locationLayer = new L.LayerGroup();
window.plugin.userLocation.setup = function() {
var iconImage = '@@INCLUDEIMAGE:images/marker-icon.png@@';
var iconRetImage = '@@INCLUDEIMAGE:images/marker-icon_2x.png@@';
plugin.userLocation.icon = L.Icon.Default.extend({options: {
iconUrl: iconImage,
iconRetinaUrl: iconRetImage
}});
var marker = L.marker(window.map.getCenter(), {
title: "User Location",
icon: new plugin.userLocation.icon()
});
plugin.userLocation.marker = marker;
marker.addTo(window.map);
// jQueryUI doesnt automatically notice the new markers
window.setupTooltips($(marker._icon));
};
window.plugin.userLocation.updateLocation = function(lat, lng) {
var latlng = new L.LatLng(lat, lng);
window.plugin.userLocation.marker.setLatLng(latlng);
}
var setup = window.plugin.userLocation.setup;
// PLUGIN END //////////////////////////////////////////////////////////
if(window.iitcLoaded && typeof setup === 'function') {
setup();
} else {
if(window.bootPlugins)
window.bootPlugins.push(setup);
else
window.bootPlugins = [setup];
}
} // wrapper end
// inject code into site context
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ wrapper +')();'));
(document.body || document.head || document.documentElement).appendChild(script);

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @id iitc-plugin-keys-on-map@xelio
// @name IITC plugin: Keys on map
// @version 0.1.0.@@DATETIMEVERSION@@
// @version 0.2.0.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@ -90,7 +90,7 @@ window.plugin.keysOnMap.disableMessage = function() {
}
}
var setup = function() {
window.plugin.keysOnMap.setupCSS = function() {
$("<style>")
.prop("type", "text/css")
.html(".plugin-keys-on-map-key {\
@ -103,9 +103,18 @@ var setup = function() {
-webkit-text-size-adjust:none;\
}")
.appendTo("head");
}
window.plugin.keysOnMap.setupLayer = function() {
window.layerChooser.addOverlay(window.plugin.keysOnMap.keyLayerGroup, 'Keys');
if(isLayerGroupDisplayed('Keys'))
map.addLayer(window.plugin.keysOnMap.keyLayerGroup);
}
var setup = function() {
window.plugin.keysOnMap.setupCSS();
window.plugin.keysOnMap.setupLayer();
// Avoid error if this plugin load first
if($.inArray('pluginKeysUpdateKey', window.VALID_HOOKS) < 0)

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @id iitc-plugin-player-tracker@breunigs
// @name IITC Plugin: Player tracker
// @version 0.9.2.@@DATETIMEVERSION@@
// @version 0.9.3.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@ -49,6 +49,14 @@ window.plugin.playerTracker.setup = function() {
plugin.playerTracker.drawnTraces = new L.LayerGroup();
window.layerChooser.addOverlay(plugin.playerTracker.drawnTraces, 'Player Tracker');
map.addLayer(plugin.playerTracker.drawnTraces);
map.on('layeradd',function(obj) {
if(obj.layer === plugin.playerTracker.drawnTraces)
{
obj.layer.eachLayer(function(marker) {
if(marker._icon) window.setupTooltips($(marker._icon));
});
}
});
plugin.playerTracker.oms = new OverlappingMarkerSpiderfier(map);
plugin.playerTracker.oms.legColors = {'usual': '#FFFF00', 'highlighted': '#FF0000'};
plugin.playerTracker.oms.legWeight = 3.5;

View File

@ -0,0 +1,100 @@
// ==UserScript==
// @id iitc-plugin-portal-level-numbers@rongou
// @name IITC plugin: Portal Level Numbers
// @version 0.1.0.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Show portal level numbers on map.
// @include https://www.ingress.com/intel*
// @include http://www.ingress.com/intel*
// @match https://www.ingress.com/intel*
// @match http://www.ingress.com/intel*
// ==/UserScript==
function wrapper() {
// ensure plugin framework is there, even if iitc is not yet loaded
if(typeof window.plugin !== 'function') window.plugin = function() {};
// PLUGIN START ////////////////////////////////////////////////////////
// use own namespace for plugin
window.plugin.portalLevelNumbers = function() {};
window.plugin.portalLevelNumbers.levelLayers = {};
window.plugin.portalLevelNumbers.levelLayerGroup = new L.LayerGroup();
// Use portal add and remove event to control render of portal level numbers
window.plugin.portalLevelNumbers.portalAdded = function(data) {
data.portal.on('add', function() {
plugin.portalLevelNumbers.renderLevel(this.options.guid, this.getLatLng());
});
data.portal.on('remove', function() {
plugin.portalLevelNumbers.removeLevel(this.options.guid);
});
}
window.plugin.portalLevelNumbers.renderLevel = function(guid,latLng) {
plugin.portalLevelNumbers.removeLevel(guid);
var d = window.portals[guid].options.details;
var levelNumber = Math.floor(window.getPortalLevel(d));
var level = L.marker(latLng, {
icon: L.divIcon({
className: 'plugin-portal-level-numbers',
iconAnchor: [6,7],
iconSize: [12,10],
html: levelNumber
}),
guid: guid
});
plugin.portalLevelNumbers.levelLayers[guid] = level;
level.addTo(plugin.portalLevelNumbers.levelLayerGroup);
}
window.plugin.portalLevelNumbers.removeLevel = function(guid) {
var previousLayer = plugin.portalLevelNumbers.levelLayers[guid];
if(previousLayer) {
plugin.portalLevelNumbers.levelLayerGroup.removeLayer(previousLayer);
delete plugin.portalLevelNumbers.levelLayers[guid];
}
}
var setup = function() {
$("<style>")
.prop("type", "text/css")
.html(".plugin-portal-level-numbers {\
font-size: 10px;\
color: #FFFFBB;\
font-family: monospace;\
text-align: center;\
text-shadow: 0 0 0.5em black, 0 0 0.5em black, 0 0 0.5em black;\
pointer-events: none;\
-webkit-text-size-adjust:none;\
}")
.appendTo("head");
window.layerChooser.addOverlay(window.plugin.portalLevelNumbers.levelLayerGroup, 'Portal Levels');
map.addLayer(window.plugin.portalLevelNumbers.levelLayerGroup);
window.addHook('portalAdded', window.plugin.portalLevelNumbers.portalAdded);
}
// PLUGIN END //////////////////////////////////////////////////////////
if(window.iitcLoaded && typeof setup === 'function') {
setup();
} else {
if(window.bootPlugins)
window.bootPlugins.push(setup);
else
window.bootPlugins = [setup];
}
} // wrapper end
// inject code into site context
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ wrapper +')();'));
(document.body || document.head || document.documentElement).appendChild(script);

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,11 @@
/*
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(j,f){function s(a,b){var c=a.createElement("p"),m=a.getElementsByTagName("head")[0]||a.documentElement;c.innerHTML="x<style>"+b+"</style>";return m.insertBefore(c.lastChild,m.firstChild)}function o(){var a=d.elements;return"string"==typeof a?a.split(" "):a}function n(a){var b=t[a[u]];b||(b={},p++,a[u]=p,t[p]=b);return b}function v(a,b,c){b||(b=f);if(e)return b.createElement(a);c||(c=n(b));b=c.cache[a]?c.cache[a].cloneNode():y.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);
return b.canHaveChildren&&!z.test(a)?c.frag.appendChild(b):b}function A(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();a.createElement=function(c){return!d.shivMethods?b.createElem(c):v(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+o().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(d,b.frag)}
function w(a){a||(a=f);var b=n(a);if(d.shivCSS&&!q&&!b.hasCSS)b.hasCSS=!!s(a,"article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}");e||A(a,b);return a}function B(a){for(var b,c=a.attributes,m=c.length,f=a.ownerDocument.createElement(l+":"+a.nodeName);m--;)b=c[m],b.specified&&f.setAttribute(b.nodeName,b.nodeValue);f.style.cssText=a.style.cssText;return f}function x(a){function b(){clearTimeout(d._removeSheetTimer);c&&c.removeNode(!0);
c=null}var c,f,d=n(a),e=a.namespaces,j=a.parentWindow;if(!C||a.printShived)return a;"undefined"==typeof e[l]&&e.add(l);j.attachEvent("onbeforeprint",function(){b();var g,i,d;d=a.styleSheets;for(var e=[],h=d.length,k=Array(h);h--;)k[h]=d[h];for(;d=k.pop();)if(!d.disabled&&D.test(d.media)){try{g=d.imports,i=g.length}catch(j){i=0}for(h=0;h<i;h++)k.push(g[h]);try{e.push(d.cssText)}catch(n){}}g=e.reverse().join("").split("{");i=g.length;h=RegExp("(^|[\\s,>+~])("+o().join("|")+")(?=[[\\s,>+~#.:]|$)","gi");
for(k="$1"+l+"\\:$2";i--;)e=g[i]=g[i].split("}"),e[e.length-1]=e[e.length-1].replace(h,k),g[i]=e.join("}");e=g.join("{");i=a.getElementsByTagName("*");h=i.length;k=RegExp("^(?:"+o().join("|")+")$","i");for(d=[];h--;)g=i[h],k.test(g.nodeName)&&d.push(g.applyElement(B(g)));f=d;c=s(a,e)});j.attachEvent("onafterprint",function(){for(var a=f,c=a.length;c--;)a[c].removeNode();clearTimeout(d._removeSheetTimer);d._removeSheetTimer=setTimeout(b,500)});a.printShived=!0;return a}var r=j.html5||{},z=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,
y=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q,u="_html5shiv",p=0,t={},e;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";q="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}e=b}catch(d){e=q=!0}})();var d={elements:r.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",
version:"3.6.2",shivCSS:!1!==r.shivCSS,supportsUnknownElements:e,shivMethods:!1!==r.shivMethods,type:"default",shivDocument:w,createElement:v,createDocumentFragment:function(a,b){a||(a=f);if(e)return a.createDocumentFragment();for(var b=b||n(a),c=b.frag.cloneNode(),d=0,j=o(),l=j.length;d<l;d++)c.createElement(j[d]);return c}};j.html5=d;w(f);var D=/^$|\b(?:all|print)\b/,l="html5shiv",C=!e&&function(){var a=f.documentElement;return!("undefined"==typeof f.namespaces||"undefined"==typeof f.parentWindow||
"undefined"==typeof a.applyElement||"undefined"==typeof a.removeNode||"undefined"==typeof j.attachEvent)}();d.type+=" print";d.shivPrint=x;x(f)})(this,document);

8
website/assets/html5shiv/html5shiv.js vendored Normal file
View File

@ -0,0 +1,8 @@
/*
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,40 +1,3 @@
<?php
$path = "release";
if ( $_REQUEST['build'] == 'dev' )
$path = "dev";
function loadUserScriptHeader($path)
{
$result = Array();
$f = fopen ( $path, "rt" );
while ( ( $line = fgets ( $f ) ) !== FALSE )
{
if ( preg_match ( '#//[ \\t]*==/UserScript==#', $line ) )
break;
$matches = Array();
if ( preg_match ( '#^//[ \\t]*(@[a-zA-Z0-9]+)[ \\t]+(.*)$#', $line, $matches ) )
{
$name = $matches[1];
$value = $matches[2];
if ( ! array_key_exists ( $name, $result ) )
{
$result[$name] = $value;
}
}
}
fclose ( $f );
return $result;
}
?>
<!DOCTYPE html>
<html lang="en">
@ -53,9 +16,12 @@ function loadUserScriptHeader($path)
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="assets/js/html5shiv.js"></script>
<script src="assets/html5shiv/html5shiv.js"></script>
<![endif]-->
<!-- android uses the apple icons when adding shortcuts - looks better than favicons -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-precomposed.png">
<style>
.nowrap { white-space: nowrap; }
</style>
@ -114,8 +80,10 @@ $pages = Array (
'faq' => '<i class="icon-question-sign"></i> FAQ',
'desktop' => '<i class="icon-chevron-right"></i> Desktop',
'mobile' => '<i class="icon-chevron-right"></i> Mobile',
'test' => '<i class="icon-wrench"></i> Test Builds',
'developer' => '<i class="icon-cog"></i> Developers',
'about' => '<i class="icon-info-sign"></i> About',
'donate' => '<i class="icon-gift"></i> Donate',
);
$page = $_REQUEST['page'];
@ -137,7 +105,7 @@ foreach ( $pages as $key => $name )
print "<li".($page == $key ? ' class="active"' :'')."><a href=\"$url\">$name</a></li>\n";
# after 'mobile', add a horizontal seperator
if ( $key == 'mobile' )
if ( $key == 'test' )
print "<li class=\"divider\"></li>";
}

View File

@ -0,0 +1,76 @@
<?php
include_once dirname(__FILE__) . "/lib/ApkArchive.php";
include_once dirname(__FILE__) . "/lib/ApkXmlParser.php";
include_once dirname(__FILE__) . "/lib/ApkManifest.php";
/**
* @author Tufan Baris YILDIRIM
* @version v0.1
* @since 27.03.2012
* @link https://github.com/tufanbarisyildirim/php-apk-parser
*
* Main Class.
* - Set the apk path on construction,
* - Get the Manifest object.
* - Print the Manifest XML.
*
* @todo Add getPackageName();
* @todo Add getVersion();
* @todo Add getUsesSdk();
* @todo Add getMinSdk();
*/
class ApkParser
{
/**
* @var ApkArchive
*/
private $apk;
/**
* AndrodiManifest.xml
*
* @var ApkManifest
*/
private $manifest;
public function __construct($apkFile)
{
$this->apk = new ApkArchive($apkFile);
$this->manifest = new ApkManifest(new ApkXmlParser($this->apk->getManifestStream()));
}
/**
* Get Manifest Object
* @return ApkManifest
*/
public function getManifest()
{
return $this->manifest;
}
/**
* Get the apk. Zip handler.
* - Extract all(or sp. entries) files,
* - add file,
* - recompress
* - and other ZipArchive features.
*
* @return ApkArchive
*/
public function getApkArchive()
{
return $this->apk;
}
/**
* Extract apk content directly
*
* @param mixed $destination
* @param array $entries
* @return bool
*/
public function extractTo($destination,$entries = NULL)
{
return $this->apk->extractTo($destination,$entries);
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @todo : Write comments!
*/
class ApkAndroidPlatform
{
public $level = NULL;
public $platform = NULL;
private static $platforms = array(
2 => array('name' => 'Android 1.1 Platfrom'),
3 => array('name' => 'Android 1.5 Platfrom'),
4 => array('name' => 'Android 1.6 Platfrom'),
5 => array('name' => 'Android 2.0 Platfrom'),
6 => array('name' => 'Android 2.0.1 Platfrom'),
7 => array('name' => 'Android 2.1 Platfrom'),
8 => array('name' => 'Android 2.2 Platfrom'),
9 => array('name' => 'Android 2.3 Platfrom'),
10 => array('name' => 'Android 2.3.3 Platfrom'),
10 => array('name' => 'Android 2.3.3 / 2.3.4 Platfroms'),
11 => array('name' => 'Android 3.0 Platfroms'),
12 => array('name' => 'Android 3.1 Platfroms'),
13 => array('name' => 'Android 3.2 Platfroms'),
14 => array('name' => 'Android 4.0 Platfroms'),
14 => array('name' => 'Android 4.0.3 Platfroms'),
);
public function __construct($apiLevel)
{
$this->level = $apiLevel;
$this->platform = $this->getPlatform();
}
/**
* @return array
*/
public function getPlatform()
{
if(!isset(self::$platforms[$this->level]))
throw new Exception("Unknown api level.");
$platform = self::$platforms[$this->level];
$platform['level'] = $this->level;
return $platform;
}
}

View File

@ -0,0 +1,117 @@
<?php
include_once dirname(__FILE__) . "/ApkStream.php";
/**
* Customized ZipArchive for .apk files.
* @author Tufan Baris YILDIRIM
* @TODO Add ->getResource('file_name'), or getIcon() directly.
* @todo Override the // extractTo() method. Rewrite all of XML files converted from Binary Xml to text based XML!
*/
class ApkArchive extends ZipArchive
{
/**
* @var string
*/
private $filePath;
/**
* @var string
*/
private $fileName;
public function __construct($file = false)
{
if($file && is_file($file))
{
$this->open($file);
$this->fileName = basename($this->filePath = $file);
}
else
throw new Exception($file . " not a regular file");
}
/**
* Get a file from apk Archive by name.
*
* @param string $name
* @param int $length
* @param int $flags
* @return mixed
*/
public function getFromName($name,$length = NULL,$flags = NULL)
{
if(strtolower(substr($name,-4)) == '.xml')
{
$xmlParser = new ApkXmlParser(new ApkStream($this->getStream($name)));
return $xmlParser->getXmlString();
}
else
return parent::getFromName($name,$length,$flags);
}
/**
* Returns an ApkStream whick contains AndroidManifest.xml
* @return ApkStream
*/
public function getManifestStream()
{
return new ApkStream($this->getStream('AndroidManifest.xml'));
}
/**
* Apk file path.
* @return string
*/
public function getApkPath()
{
return $this->filePath;
}
/**
* Apk file name
* @return string
*/
public function getApkName()
{
return $this->fileName;
}
public function extractTo($destination,$entries = NULL)
{
if($extResult = parent::extractTo($destination,$entries))
{
//TODO: ApkXmlParser can not parse the main.xml and others! only AndroidManifest.xml
//return $extResult;
$xmlFiles = $this->glob_recursive($destination . '/*.xml');
foreach($xmlFiles as $xmlFile)
{
// TODO : Remove this ifcheck , if ApkXml can parse! amk!
if($xmlFile == "AndroidManifest.xml")
ApkXmlParser::decompressFile($xmlFile);
}
}
return $extResult;
}
// Can Move to the Utils(???) class.
private function glob_recursive($pattern, $flags = 0)
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
{
$files = array_merge($files, $this->glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}

View File

@ -0,0 +1,269 @@
<?php
include_once dirname(__FILE__). '/ApkXml.php';
include_once dirname(__FILE__). '/ApkManifestXmlElement.php';
include_once dirname(__FILE__). '/ApkAndroidPlatform.php';
/**
* ApkManifest
* -- description is coming.
*
* @todo Add getPackageName();
* @todo Add getVersion();
* @todo Add getUsesSdk();
* @todo Add getMinSdk();
*/
class ApkManifest extends ApkXml
{
/**
* @var ApkXmlParser
*/
private $xmlParser;
private $attrs = null;
public function __construct(ApkXmlParser $xmlParser)
{
$this->xmlParser = $xmlParser;
}
/**
* Returns ManifestXml as a String.
* @return string
*/
public function getXmlString()
{
return $this->xmlParser->getXmlString();
}
/**
* Get Application Permissions
* @return array
*/
public function getPermissions()
{
return $this->getXmlObject()->getPermissions();
}
/**
* Android Package Name
* @return string
*/
public function getPackageName()
{
return $this->getAttribute('package');
}
/**
* Application Version Name
* @return string
*/
public function getVersionName()
{
return $this->getAttribute('versionName');
}
/**
* Application Version Code
* @return mixed
*/
public function getVersionCode()
{
return hexdec( $this->getAttribute('versionCode') );
}
/**
* @return bool
*/
public function isDebuggable()
{
return (bool)$this->getAttribute('debuggable');
}
/**
* The minimum API Level required for the application to run.
* @return int
*/
public function getMinSdkLevel()
{
$xmlObj = $this->getXmlObject();
$usesSdk = get_object_vars($xmlObj->{'uses-sdk'});
return hexdec($usesSdk['@attributes']['minSdkVersion']);
}
private function getAttribute($attributeName)
{
if($this->attrs === NULL)
{
$xmlObj = $this->getXmlObject();
$vars = get_object_vars($xmlObj->attributes());
$this->attrs = $vars['@attributes'];
}
if(!isset($this->attrs[$attributeName]))
throw new Exception("Attribute not found : " . $attributeName);
return $this->attrs[$attributeName];
}
/**
* More Information About The minimum API Level required for the application to run.
* @return ApkAndroidPlatform
*/
public function getMinSdk()
{
return new ApkAndroidPlatform($this->getMinSdkLevel());
}
/**
* get SimleXmlElement created from AndroidManifest.xml
*
* @param mixed $className
* @return ApkManifestXmlElement
*/
public function getXmlObject($className = 'ApkManifestXmlElement')
{
return $this->xmlParser->getXmlObject($className);
}
/**
* Basicly string casting method.
*/
public function __toString()
{
return $this->getXmlString();
}
/**
* Android Permissions list
* @see http://developer.android.com/reference/android/Manifest.permission.html
*
* @todo: Move to {lang}_perms.php file, for easly translations.
* @var mixed
*/
public static $permissions = array(
'ACCESS_CHECKIN_PROPERTIES' => 'Allows read/write access to the "properties" table in the checkin database, to change values that get uploaded.',
'ACCESS_COARSE_LOCATION' => 'Allows an application to access coarse (e.g., Cell-ID, WiFi) location',
'ACCESS_FINE_LOCATION' => 'Allows an application to access fine (e.g., GPS) location',
'ACCESS_LOCATION_EXTRA_COMMANDS' => 'Allows an application to access extra location provider commands',
'ACCESS_MOCK_LOCATION' => 'Allows an application to create mock location providers for testing',
'ACCESS_NETWORK_STATE' => 'Allows applications to access information about networks',
'ACCESS_SURFACE_FLINGER' => 'Allows an application to use SurfaceFlinger\'s low level features',
'ACCESS_WIFI_STATE' => 'Allows applications to access information about Wi-Fi networks',
'ACCOUNT_MANAGER' => 'Allows applications to call into AccountAuthenticators.',
'ADD_VOICEMAIL' => 'Allows an application to add voicemails into the system.',
'AUTHENTICATE_ACCOUNTS' => 'Allows an application to act as an AccountAuthenticator for the AccountManager',
'BATTERY_STATS' => 'Allows an application to collect battery statistics',
'BIND_APPWIDGET' => 'Allows an application to tell the AppWidget service which application can access AppWidget\'s data.',
'BIND_DEVICE_ADMIN' => 'Must be required by device administration receiver, to ensure that only the system can interact with it.',
'BIND_INPUT_METHOD' => 'Must be required by an InputMethodService, to ensure that only the system can bind to it.',
'BIND_REMOTEVIEWS' => 'Must be required by a RemoteViewsService, to ensure that only the system can bind to it.',
'BIND_TEXT_SERVICE' => 'Must be required by a TextService (e.g.',
'BIND_VPN_SERVICE' => 'Must be required by an VpnService, to ensure that only the system can bind to it.',
'BIND_WALLPAPER' => 'Must be required by a WallpaperService, to ensure that only the system can bind to it.',
'BLUETOOTH' => 'Allows applications to connect to paired bluetooth devices',
'BLUETOOTH_ADMIN' => 'Allows applications to discover and pair bluetooth devices',
'BRICK' => 'Required to be able to disable the device (very dangerous!).',
'BROADCAST_PACKAGE_REMOVED' => 'Allows an application to broadcast a notification that an application package has been removed.',
'BROADCAST_SMS' => 'Allows an application to broadcast an SMS receipt notification',
'BROADCAST_STICKY' => 'Allows an application to broadcast sticky intents.',
'BROADCAST_WAP_PUSH' => 'Allows an application to broadcast a WAP PUSH receipt notification',
'CALL_PHONE' => 'Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed.',
'CALL_PRIVILEGED' => 'Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed.',
'CAMERA' => 'Required to be able to access the camera device.',
'CHANGE_COMPONENT_ENABLED_STATE' => 'Allows an application to change whether an application component (other than its own) is enabled or not.',
'CHANGE_CONFIGURATION' => 'Allows an application to modify the current configuration, such as locale.',
'CHANGE_NETWORK_STATE' => 'Allows applications to change network connectivity state',
'CHANGE_WIFI_MULTICAST_STATE' => 'Allows applications to enter Wi-Fi Multicast mode',
'CHANGE_WIFI_STATE' => 'Allows applications to change Wi-Fi connectivity state',
'CLEAR_APP_CACHE' => 'Allows an application to clear the caches of all installed applications on the device.',
'CLEAR_APP_USER_DATA' => 'Allows an application to clear user data',
'CONTROL_LOCATION_UPDATES' => 'Allows enabling/disabling location update notifications from the radio.',
'DELETE_CACHE_FILES' => 'Allows an application to delete cache files.',
'DELETE_PACKAGES' => 'Allows an application to delete packages.',
'DEVICE_POWER' => 'Allows low-level access to power management',
'DIAGNOSTIC' => 'Allows applications to RW to diagnostic resources.',
'DISABLE_KEYGUARD' => 'Allows applications to disable the keyguard',
'DUMP' => 'Allows an application to retrieve state dump information from system services.',
'EXPAND_STATUS_BAR' => 'Allows an application to expand or collapse the status bar.',
'FACTORY_TEST' => 'Run as a manufacturer test application, running as the root user.',
'FLASHLIGHT' => 'Allows access to the flashlight',
'FORCE_BACK' => 'Allows an application to force a BACK operation on whatever is the top activity.',
'GET_ACCOUNTS' => 'Allows access to the list of accounts in the Accounts Service',
'GET_PACKAGE_SIZE' => 'Allows an application to find out the space used by any package.',
'GET_TASKS' => 'Allows an application to get information about the currently or recently running tasks: a thumbnail representation of the tasks, what activities are running in it, etc.',
'GLOBAL_SEARCH' => 'This permission can be used on content providers to allow the global search system to access their data.',
'HARDWARE_TEST' => 'Allows access to hardware peripherals.',
'INJECT_EVENTS' => 'Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window.',
'INSTALL_LOCATION_PROVIDER' => 'Allows an application to install a location provider into the Location Manager',
'INSTALL_PACKAGES' => 'Allows an application to install packages.',
'INTERNAL_SYSTEM_WINDOW' => 'Allows an application to open windows that are for use by parts of the system user interface.',
'INTERNET' => 'Allows applications to open network sockets.',
'KILL_BACKGROUND_PROCESSES' => 'Allows an application to call killBackgroundProcesses(String).',
'MANAGE_ACCOUNTS' => 'Allows an application to manage the list of accounts in the AccountManager',
'MANAGE_APP_TOKENS' => 'Allows an application to manage (create, destroy, Z-order) application tokens in the window manager.',
'MASTER_CLEAR' => '',
'MODIFY_AUDIO_SETTINGS' => 'Allows an application to modify global audio settings',
'MODIFY_PHONE_STATE' => 'Allows modification of the telephony state - power on, mmi, etc.',
'MOUNT_FORMAT_FILESYSTEMS' => 'Allows formatting file systems for removable storage.',
'MOUNT_UNMOUNT_FILESYSTEMS' => 'Allows mounting and unmounting file systems for removable storage.',
'NFC' => 'Allows applications to perform I/O operations over NFC',
'PERSISTENT_ACTIVITY' => 'This constant is deprecated. This functionality will be removed in the future; please do not use. Allow an application to make its activities persistent.',
'PROCESS_OUTGOING_CALLS' => 'Allows an application to monitor, modify, or abort outgoing calls.',
'READ_CALENDAR' => 'Allows an application to read the user\'s calendar data.',
'READ_CONTACTS' => 'Allows an application to read the user\'s contacts data.',
'READ_FRAME_BUFFER' => 'Allows an application to take screen shots and more generally get access to the frame buffer data',
'READ_HISTORY_BOOKMARKS' => 'Allows an application to read (but not write) the user\'s browsing history and bookmarks.',
'READ_INPUT_STATE' => 'Allows an application to retrieve the current state of keys and switches.',
'READ_LOGS' => 'Allows an application to read the low-level system log files.',
'READ_PHONE_STATE' => 'Allows read only access to phone state.',
'READ_PROFILE' => 'Allows an application to read the user\'s personal profile data.',
'READ_SMS' => 'Allows an application to read SMS messages.',
'READ_SOCIAL_STREAM' => 'Allows an application to read from the user\'s social stream.',
'READ_SYNC_SETTINGS' => 'Allows applications to read the sync settings',
'READ_SYNC_STATS' => 'Allows applications to read the sync stats',
'REBOOT' => 'Required to be able to reboot the device.',
'RECEIVE_BOOT_COMPLETED' => 'Allows an application to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting.',
'RECEIVE_MMS' => 'Allows an application to monitor incoming MMS messages, to record or perform processing on them.',
'RECEIVE_SMS' => 'Allows an application to monitor incoming SMS messages, to record or perform processing on them.',
'RECEIVE_WAP_PUSH' => 'Allows an application to monitor incoming WAP push messages.',
'RECORD_AUDIO' => 'Allows an application to record audio',
'REORDER_TASKS' => 'Allows an application to change the Z-order of tasks',
'RESTART_PACKAGES' => 'This constant is deprecated. The restartPackage(String) API is no longer supported.',
'SEND_SMS' => 'Allows an application to send SMS messages.',
'SET_ACTIVITY_WATCHER' => 'Allows an application to watch and control how activities are started globally in the system.',
'SET_ALARM' => 'Allows an application to broadcast an Intent to set an alarm for the user.',
'SET_ALWAYS_FINISH' => 'Allows an application to control whether activities are immediately finished when put in the background.',
'SET_ANIMATION_SCALE' => 'Modify the global animation scaling factor.',
'SET_DEBUG_APP' => 'Configure an application for debugging.',
'SET_ORIENTATION' => 'Allows low-level access to setting the orientation (actually rotation) of the screen.',
'SET_POINTER_SPEED' => 'Allows low-level access to setting the pointer speed.',
'SET_PREFERRED_APPLICATIONS' => 'This constant is deprecated. No longer useful, see addPackageToPreferred(String) for details.',
'SET_PROCESS_LIMIT' => 'Allows an application to set the maximum number of (not needed) application processes that can be running.',
'SET_TIME' => 'Allows applications to set the system time',
'SET_TIME_ZONE' => 'Allows applications to set the system time zone',
'SET_WALLPAPER' => 'Allows applications to set the wallpaper',
'SET_WALLPAPER_HINTS' => 'Allows applications to set the wallpaper hints',
'SIGNAL_PERSISTENT_PROCESSES' => 'Allow an application to request that a signal be sent to all persistent processes',
'STATUS_BAR' => 'Allows an application to open, close, or disable the status bar and its icons.',
'SUBSCRIBED_FEEDS_READ' => 'Allows an application to allow access the subscribed feeds ContentProvider.',
'SUBSCRIBED_FEEDS_WRITE' => '',
'SYSTEM_ALERT_WINDOW' => 'Allows an application to open windows using the type TYPE_SYSTEM_ALERT, shown on top of all other applications.',
'UPDATE_DEVICE_STATS' => 'Allows an application to update device statistics.',
'USE_CREDENTIALS' => 'Allows an application to request authtokens from the AccountManager',
'USE_SIP' => 'Allows an application to use SIP service',
'VIBRATE' => 'Allows access to the vibrator',
'WAKE_LOCK' => 'Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming',
'WRITE_APN_SETTINGS' => 'Allows applications to write the apn settings',
'WRITE_CALENDAR' => 'Allows an application to write (but not read) the user\'s calendar data.',
'WRITE_CONTACTS' => 'Allows an application to write (but not read) the user\'s contacts data.',
'WRITE_EXTERNAL_STORAGE' => 'Allows an application to write to external storage',
'WRITE_GSERVICES' => 'Allows an application to modify the Google service map.',
'WRITE_HISTORY_BOOKMARKS' => 'Allows an application to write (but not read) the user\'s browsing history and bookmarks.',
'WRITE_PROFILE' => 'Allows an application to write (but not read) the user\'s personal profile data.',
'WRITE_SECURE_SETTINGS' => 'Allows an application to read or write the secure system settings.',
'WRITE_SETTINGS' => 'Allows an application to read or write the system settings.',
'WRITE_SMS' => 'Allows an application to write SMS messages.',
'WRITE_SOCIAL_STREAM' => 'Allows an application to write (but not read) the user\'s social stream data.',
'WRITE_SYNC_SETTINGS' => 'Allows applications to write the sync settings'
);
}

View File

@ -0,0 +1,22 @@
<?php
class ApkManifestXmlElement extends SimpleXMLElement
{
public function getPermissions()
{
/**
* @var ApkManifestXmlElement
*/
$permsArray = $this->{'uses-permission'};
$perms = array();
foreach($permsArray as $perm)
{
$permAttr = get_object_vars($perm);
$objNotationArray = explode('.',$permAttr['@attributes']['name']);
$permName = trim(end($objNotationArray));
$perms[$permName] = ApkManifest::$permissions[$permName];
}
return $perms;
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* @author Tufan Baris YILDIRIM
* -- descrtiption is coming.
*/
class ApkStream
{
/**
* file strem, like "fopen"
*
* @var resource
*/
private $stream;
/**
* @param resource $stream File stream.
* @return ApkStream
*/
public function __construct($stream)
{
if(!is_resource($stream))
// TODO : the resource type must be a regular file stream resource.
throw new Exception( "Invalid stream" );
$this->stream = $stream;
}
/**
* Read the next character from stream.
*
* @param mixed $length
*/
public function read($length = 1)
{
return fread($this->stream,$length);
}
/**
* check if end of filestream
*/
public function feof()
{
return feof($this->stream);
}
/**
* Jump to the index!
* @param int $offset
*/
public function seek($offset)
{
fseek($this->stream,$offset);
}
/**
* Close the stream
*/
public function close()
{
fclose($this->stream);
}
/**
* Read the next byte
* @return byte
*/
public function readByte()
{
return ord($this->read());
}
/**
* fetch the remaining byte into an array
*
* @param mixed $count Byte length.
* @return array
*/
public function getByteArray($count = null)
{
$bytes = array();
while(!$this->feof() && ($count === null || count($bytes) < $count))
$bytes[] = $this->readByte();
return $bytes;
}
}

View File

@ -0,0 +1,5 @@
<?php
abstract class ApkXml
{
}

View File

@ -0,0 +1,181 @@
<?php
include_once dirname(__FILE__) . "/ApkStream.php";
class ApkXmlParser
{
const END_DOC_TAG = 0x00100101;
const START_TAG = 0x00100102;
const END_TAG = 0x00100103;
private $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
private $bytes = array();
private $ready = false;
public static $indent_spaces = " ";
/**
* Store the SimpleXmlElement object
* @var SimpleXmlElement
*/
private $xmlObject = NULL;
public function __construct(ApkStream $apkStream)
{
$this->bytes = $apkStream->getByteArray();
}
public static function decompressFile($file,$destination = NULL)
{
if(!is_file($file))
throw new Exception("{$file} is not a regular file");
$parser = new self(new ApkStream(fopen($file,'rd')));
//TODO : write a method in this class, ->saveToFile();
file_put_contents($destination === NULL ? $file : $destination,$parser->getXmlString());
}
public function decompress()
{
$numbStrings = $this->littleEndianWord($this->bytes, 4*4);
$sitOff = 0x24;
$stOff = $sitOff + $numbStrings * 4;
$this->bytesTagOff = $this->littleEndianWord($this->bytes, 3*4);
for ($ii = $this->bytesTagOff; $ii < count($this->bytes) - 4; $ii += 4):
if ($this->littleEndianWord($this->bytes, $ii) == self::START_TAG) :
$this->bytesTagOff = $ii;
break;
endif;
endfor;
$off = $this->bytesTagOff;
$indentCount = 0;
$startTagLineNo = -2;
while ($off < count($this->bytes))
{
$currentTag = $this->littleEndianWord($this->bytes, $off);
$lineNo = $this->littleEndianWord($this->bytes, $off + 2*4);
$nameNsSi = $this->littleEndianWord($this->bytes, $off + 4*4);
$nameSi = $this->littleEndianWord($this->bytes, $off + 5*4);
switch($currentTag)
{
case self::START_TAG:
{
$tagSix = $this->littleEndianWord($this->bytes, $off + 6*4);
$numbAttrs = $this->littleEndianWord($this->bytes, $off + 7*4);
$off += 9*4;
$tagName = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
$startTagLineNo = $lineNo;
$attr_string = "";
for ($ii=0; $ii < $numbAttrs; $ii++)
{
$attrNameNsSi = $this->littleEndianWord($this->bytes, $off);
$attrNameSi = $this->littleEndianWord($this->bytes, $off + 1*4);
$attrValueSi = $this->littleEndianWord($this->bytes, $off + 2*4);
$attrFlags = $this->littleEndianWord($this->bytes, $off + 3*4);
$attrResId = $this->littleEndianWord($this->bytes, $off + 4*4);
$off += 5*4;
$attrName = $this->compXmlString($this->bytes, $sitOff, $stOff, $attrNameSi);
if($attrValueSi != 0xffffffff)
$attrValue = $this->compXmlString($this->bytes, $sitOff, $stOff, $attrValueSi);
else
$attrValue = "0x" . dechex($attrResId);
$attr_string .= " " . $attrName . "=\"" . $attrValue . "\"";
}
$this->appendXmlIndent($indentCount, "<". $tagName . $attr_string . ">");
$indentCount++;
}
break;
case self::END_TAG:
{
$indentCount--;
$off += 6*4;
$tagName = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
$this->appendXmlIndent($indentCount, "</" . $tagName . ">");
}
break;
case self::END_DOC_TAG:
{
$this->ready = true;
break 2;
}
break;
default:
throw new Exception("Unrecognized tag code '" . dechex($currentTag) . "' at offset " . $off);
break;
}
}
}
public function compXmlString($xml, $sitOff, $stOff, $str_index)
{
if ($str_index < 0)
return null;
$strOff = $stOff + $this->littleEndianWord($xml, $sitOff + $str_index * 4);
return $this->compXmlStringAt($xml, $strOff);
}
public function appendXmlIndent($indent, $str)
{
$this->appendXml(substr(self::$indent_spaces,0, min($indent * 2, strlen(self::$indent_spaces))) . $str);
}
public function appendXml($str)
{
$this->xml .= $str ."\r\n";
}
public function compXmlStringAt($arr, $string_offset)
{
$strlen = $arr[$string_offset + 1] << 8 & 0xff00 | $arr[$string_offset] & 0xff;
$string = "";
for ($i=0; $i<$strlen; $i++)
$string .= chr($arr[$string_offset + 2 + $i * 2]);
return $string;
}
public function littleEndianWord($arr, $off)
{
return $arr[$off+3] << 24&0xff000000 | $arr[$off+2] << 16&0xff0000 | $arr[$off+1]<<8&0xff00 | $arr[$off]&0xFF;
}
public function output()
{
echo $this->getXmlString();
}
public function getXmlString()
{
if(!$this->ready)
$this->decompress();
return $this->xml;
}
public function getXmlObject($className = 'SimpleXmlElement')
{
if($this->xmlObject === NULL || !$this->xmlObject instanceof $className)
$this->xmlObject = simplexml_load_string($this->getXmlString(),$className);
return $this->xmlObject;
}
}

View File

@ -0,0 +1,64 @@
<?php
include_once ( "userscript.php" );
function iitcDesktopDownload ( $build )
{
$iitc_details = loadUserScriptHeader ( "$build/total-conversion-build.user.js" );
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $iitc_details['@version'] );
print "<p>IITC version $iitc_version</p>\n";
print "<a class=\"btn btn-large btn-primary\" onclick=\"if(track){track('desktop','iitc','$build');}\" href=\"$build/total-conversion-build.user.js\">Download</a>\n";
}
function iitcDesktopPluginDownloadTable ( $build )
{
?>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>ID / Version</th>
<th>Description</th>
<th>Download</th>
</tr>
</thead>
<tbody>
<?php
foreach ( glob ( "$build/plugins/*.user.js" ) as $path )
{
$basename = basename ( $path, ".user.js" );
$details = loadUserScriptHeader ( $path );
print "<tr id=\"plugin-$basename\">\n";
# remove 'IITC Plugin: ' prefix if it's there, for neatness
$name = preg_replace ( '/^IITC plugin: /i', '', $details['@name'] );
# format extended version info in less prominant font
$version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $details['@version'] );
# remove unneeded prefix from description
$description = preg_replace ( '/^\[[^]]*\] */', '', $details['@description'] );
print "<td>$name</td>";
print "<td>$basename<br />$version</td>";
print "<td>$description</td>";
print "<td><a onclick=\"if(track){track('desktop','iitc-plugin-$basename','$build');}\" href=\"$path\" class=\"btn btn-small btn-primary\">Download</a></td>";
print "</tr>\n";
}
?>
</tbody>
</table>
<?php
}
?>

View File

@ -0,0 +1,68 @@
<?php
include_once ( "apk/ApkParser.php" );
include_once ( "url/url_to_absolute.php" );
include_once ( "userscript.php" );
function getMobileVersion ( $apkfile )
{
$result = Array();
$apkinfo = new ApkParser ( $apkfile );
$manifest = $apkinfo->getManifest();
$result['apk_version'] = $manifest->getVersionName();
$archive = $apkinfo->getApkArchive();
$iitc_file = "assets/total-conversion-build.user.js";
if ( $archive->statName ( $iitc_file ) === FALSE );
$iitc_file = "assets/iitc.js";
$stream = $archive->getStream ( $iitc_file );
$header = loadUserScriptHeader ( $stream );
$result['iitc_version'] = $header['@version'];
return $result;
}
function iitcMobileDownload ( $apkfile )
{
$version = getMobileVersion ( $apkfile );
$apk_version = $version['apk_version'];
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $version['iitc_version'] );
# we need an absolute link for the QR Code
# get the URL of this page itself
$pageurl = ($_SERVER['HTTPS'] ? "https" : "http")."://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$apkurl = url_to_absolute ( $pageurl, $apkfile );
?>
<div>
<img style="float: right; margin: 10px;" src="https://chart.googleapis.com/chart?cht=qr&chs=120x120&chld=L|2&chl=<?php print urlencode($apkurl); ?>" alt="QR Code for download">
<p>
IITC Mobile version <?php print $apk_version; ?>, with IITC version <?php print $iitc_version; ?>
</p>
<p>
<a style="margin-right: 1em;" onclick="if(track)({track{'mobile','download','<?php print $apkfile; ?>');}" class="btn btn-large btn-primary" href="<?php print $apkfile; ?>">Download</a> or scan the QR Code
</p>
</div>
<div style="clear: both"></div>
<?php
}
?>

View File

@ -0,0 +1,24 @@
Changelog
---------
absoluteurl v1.6, March 12, 2010
---------------------------------
- added encode_url function to convert an absolute url to its percentage
encoded equivalent, according to RFC 3986
absoluteurl v1.5, March 11, 2010
---------------------------------
- fixed to allow spaces in the path of url
absoluteurl v1.4, October 2, 2009
----------------------------------------------
- Percentage encoding of the absolute url disabled.
absoluteurl v1.2, 2009-02-27
-----------------------------------------------
- Minor bug fix
absoluteurl v1.0, 2009-08-28
----------------------------------------
- Initial release

View File

@ -0,0 +1,26 @@
absoluteurl
---------------------
This script converts the relative url to absolute url, provided a base url.
For more, look here: http://publicmind.in/blog/urltoabsolute
Usage:
----------
Extract the script (url_to_absolute.php) into your web directory, include it into your current php file using:
require(path-to-file);
then, you can convert the relative url to absolute url by calling:
url_to_absolute( $baseUrl, $relativeUrl);
It return false on failure, otherwise returns the absolute url. If the $relativeUrl is a valid absolute url, it is returned without any modification.
Author/credits
-----------
1) Original author: David R. Nadeau, NadeauSoftware.com
2) Edited and maintained by: Nitin Kr, Gupta, publicmind.in

View File

@ -0,0 +1,485 @@
<?php
/**
* Edited by Nitin Kr. Gupta, publicmind.in
*/
/**
* Copyright (c) 2008, David R. Nadeau, NadeauSoftware.com.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* * Neither the names of David R. Nadeau or NadeauSoftware.com, nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*/
/*
* This is a BSD License approved by the Open Source Initiative (OSI).
* See: http://www.opensource.org/licenses/bsd-license.php
*/
/**
* Combine a base URL and a relative URL to produce a new
* absolute URL. The base URL is often the URL of a page,
* and the relative URL is a URL embedded on that page.
*
* This function implements the "absolutize" algorithm from
* the RFC3986 specification for URLs.
*
* This function supports multi-byte characters with the UTF-8 encoding,
* per the URL specification.
*
* Parameters:
* baseUrl the absolute base URL.
*
* url the relative URL to convert.
*
* Return values:
* An absolute URL that combines parts of the base and relative
* URLs, or FALSE if the base URL is not absolute or if either
* URL cannot be parsed.
*/
function url_to_absolute( $baseUrl, $relativeUrl )
{
// If relative URL has a scheme, clean path and return.
$r = split_url( $relativeUrl );
if ( $r === FALSE )
return FALSE;
if ( !empty( $r['scheme'] ) )
{
if ( !empty( $r['path'] ) && $r['path'][0] == '/' )
$r['path'] = url_remove_dot_segments( $r['path'] );
return join_url( $r );
}
// Make sure the base URL is absolute.
$b = split_url( $baseUrl );
if ( $b === FALSE || empty( $b['scheme'] ) || empty( $b['host'] ) )
return FALSE;
$r['scheme'] = $b['scheme'];
// If relative URL has an authority, clean path and return.
if ( isset( $r['host'] ) )
{
if ( !empty( $r['path'] ) )
$r['path'] = url_remove_dot_segments( $r['path'] );
return join_url( $r );
}
unset( $r['port'] );
unset( $r['user'] );
unset( $r['pass'] );
// Copy base authority.
$r['host'] = $b['host'];
if ( isset( $b['port'] ) ) $r['port'] = $b['port'];
if ( isset( $b['user'] ) ) $r['user'] = $b['user'];
if ( isset( $b['pass'] ) ) $r['pass'] = $b['pass'];
// If relative URL has no path, use base path
if ( empty( $r['path'] ) )
{
if ( !empty( $b['path'] ) )
$r['path'] = $b['path'];
if ( !isset( $r['query'] ) && isset( $b['query'] ) )
$r['query'] = $b['query'];
return join_url( $r );
}
// If relative URL path doesn't start with /, merge with base path
if ( $r['path'][0] != '/' )
{
$base = mb_strrchr( $b['path'], '/', TRUE, 'UTF-8' );
if ( $base === FALSE ) $base = '';
$r['path'] = $base . '/' . $r['path'];
}
$r['path'] = url_remove_dot_segments( $r['path'] );
return join_url( $r );
}
/**
* Filter out "." and ".." segments from a URL's path and return
* the result.
*
* This function implements the "remove_dot_segments" algorithm from
* the RFC3986 specification for URLs.
*
* This function supports multi-byte characters with the UTF-8 encoding,
* per the URL specification.
*
* Parameters:
* path the path to filter
*
* Return values:
* The filtered path with "." and ".." removed.
*/
function url_remove_dot_segments( $path )
{
// multi-byte character explode
$inSegs = preg_split( '!/!u', $path );
$outSegs = array( );
foreach ( $inSegs as $seg )
{
if ( $seg == '' || $seg == '.')
continue;
if ( $seg == '..' )
array_pop( $outSegs );
else
array_push( $outSegs, $seg );
}
$outPath = implode( '/', $outSegs );
if ( $path[0] == '/' )
$outPath = '/' . $outPath;
// compare last multi-byte character against '/'
if ( $outPath != '/' &&
(mb_strlen($path)-1) == mb_strrpos( $path, '/', 'UTF-8' ) )
$outPath .= '/';
return $outPath;
}
/**
* This function parses an absolute or relative URL and splits it
* into individual components.
*
* RFC3986 specifies the components of a Uniform Resource Identifier (URI).
* A portion of the ABNFs are repeated here:
*
* URI-reference = URI
* / relative-ref
*
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
*
* relative-ref = relative-part [ "?" query ] [ "#" fragment ]
*
* hier-part = "//" authority path-abempty
* / path-absolute
* / path-rootless
* / path-empty
*
* relative-part = "//" authority path-abempty
* / path-absolute
* / path-noscheme
* / path-empty
*
* authority = [ userinfo "@" ] host [ ":" port ]
*
* So, a URL has the following major components:
*
* scheme
* The name of a method used to interpret the rest of
* the URL. Examples: "http", "https", "mailto", "file'.
*
* authority
* The name of the authority governing the URL's name
* space. Examples: "example.com", "user@example.com",
* "example.com:80", "user:password@example.com:80".
*
* The authority may include a host name, port number,
* user name, and password.
*
* The host may be a name, an IPv4 numeric address, or
* an IPv6 numeric address.
*
* path
* The hierarchical path to the URL's resource.
* Examples: "/index.htm", "/scripts/page.php".
*
* query
* The data for a query. Examples: "?search=google.com".
*
* fragment
* The name of a secondary resource relative to that named
* by the path. Examples: "#section1", "#header".
*
* An "absolute" URL must include a scheme and path. The authority, query,
* and fragment components are optional.
*
* A "relative" URL does not include a scheme and must include a path. The
* authority, query, and fragment components are optional.
*
* This function splits the $url argument into the following components
* and returns them in an associative array. Keys to that array include:
*
* "scheme" The scheme, such as "http".
* "host" The host name, IPv4, or IPv6 address.
* "port" The port number.
* "user" The user name.
* "pass" The user password.
* "path" The path, such as a file path for "http".
* "query" The query.
* "fragment" The fragment.
*
* One or more of these may not be present, depending upon the URL.
*
* Optionally, the "user", "pass", "host" (if a name, not an IP address),
* "path", "query", and "fragment" may have percent-encoded characters
* decoded. The "scheme" and "port" cannot include percent-encoded
* characters and are never decoded. Decoding occurs after the URL has
* been parsed.
*
* Parameters:
* url the URL to parse.
*
* decode an optional boolean flag selecting whether
* to decode percent encoding or not. Default = TRUE.
*
* Return values:
* the associative array of URL parts, or FALSE if the URL is
* too malformed to recognize any parts.
*/
function split_url( $url, $decode=FALSE)
{
// Character sets from RFC3986.
$xunressub = 'a-zA-Z\d\-._~\!$&\'()*+,;=';
$xpchar = $xunressub . ':@% ';
// Scheme from RFC3986.
$xscheme = '([a-zA-Z][a-zA-Z\d+-.]*)';
// User info (user + password) from RFC3986.
$xuserinfo = '(([' . $xunressub . '%]*)' .
'(:([' . $xunressub . ':%]*))?)';
// IPv4 from RFC3986 (without digit constraints).
$xipv4 = '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})';
// IPv6 from RFC2732 (without digit and grouping constraints).
$xipv6 = '(\[([a-fA-F\d.:]+)\])';
// Host name from RFC1035. Technically, must start with a letter.
// Relax that restriction to better parse URL structure, then
// leave host name validation to application.
$xhost_name = '([a-zA-Z\d-.%]+)';
// Authority from RFC3986. Skip IP future.
$xhost = '(' . $xhost_name . '|' . $xipv4 . '|' . $xipv6 . ')';
$xport = '(\d*)';
$xauthority = '((' . $xuserinfo . '@)?' . $xhost .
'?(:' . $xport . ')?)';
// Path from RFC3986. Blend absolute & relative for efficiency.
$xslash_seg = '(/[' . $xpchar . ']*)';
$xpath_authabs = '((//' . $xauthority . ')((/[' . $xpchar . ']*)*))';
$xpath_rel = '([' . $xpchar . ']+' . $xslash_seg . '*)';
$xpath_abs = '(/(' . $xpath_rel . ')?)';
$xapath = '(' . $xpath_authabs . '|' . $xpath_abs .
'|' . $xpath_rel . ')';
// Query and fragment from RFC3986.
$xqueryfrag = '([' . $xpchar . '/?' . ']*)';
// URL.
$xurl = '^(' . $xscheme . ':)?' . $xapath . '?' .
'(\?' . $xqueryfrag . ')?(#' . $xqueryfrag . ')?$';
// Split the URL into components.
if ( !preg_match( '!' . $xurl . '!', $url, $m ) )
return FALSE;
if ( !empty($m[2]) ) $parts['scheme'] = strtolower($m[2]);
if ( !empty($m[7]) ) {
if ( isset( $m[9] ) ) $parts['user'] = $m[9];
else $parts['user'] = '';
}
if ( !empty($m[10]) ) $parts['pass'] = $m[11];
if ( !empty($m[13]) ) $h=$parts['host'] = $m[13];
else if ( !empty($m[14]) ) $parts['host'] = $m[14];
else if ( !empty($m[16]) ) $parts['host'] = $m[16];
else if ( !empty( $m[5] ) ) $parts['host'] = '';
if ( !empty($m[17]) ) $parts['port'] = $m[18];
if ( !empty($m[19]) ) $parts['path'] = $m[19];
else if ( !empty($m[21]) ) $parts['path'] = $m[21];
else if ( !empty($m[25]) ) $parts['path'] = $m[25];
if ( !empty($m[27]) ) $parts['query'] = $m[28];
if ( !empty($m[29]) ) $parts['fragment']= $m[30];
if ( !$decode )
return $parts;
if ( !empty($parts['user']) )
$parts['user'] = rawurldecode( $parts['user'] );
if ( !empty($parts['pass']) )
$parts['pass'] = rawurldecode( $parts['pass'] );
if ( !empty($parts['path']) )
$parts['path'] = rawurldecode( $parts['path'] );
if ( isset($h) )
$parts['host'] = rawurldecode( $parts['host'] );
if ( !empty($parts['query']) )
$parts['query'] = rawurldecode( $parts['query'] );
if ( !empty($parts['fragment']) )
$parts['fragment'] = rawurldecode( $parts['fragment'] );
return $parts;
}
/**
* This function joins together URL components to form a complete URL.
*
* RFC3986 specifies the components of a Uniform Resource Identifier (URI).
* This function implements the specification's "component recomposition"
* algorithm for combining URI components into a full URI string.
*
* The $parts argument is an associative array containing zero or
* more of the following:
*
* "scheme" The scheme, such as "http".
* "host" The host name, IPv4, or IPv6 address.
* "port" The port number.
* "user" The user name.
* "pass" The user password.
* "path" The path, such as a file path for "http".
* "query" The query.
* "fragment" The fragment.
*
* The "port", "user", and "pass" values are only used when a "host"
* is present.
*
* The optional $encode argument indicates if appropriate URL components
* should be percent-encoded as they are assembled into the URL. Encoding
* is only applied to the "user", "pass", "host" (if a host name, not an
* IP address), "path", "query", and "fragment" components. The "scheme"
* and "port" are never encoded. When a "scheme" and "host" are both
* present, the "path" is presumed to be hierarchical and encoding
* processes each segment of the hierarchy separately (i.e., the slashes
* are left alone).
*
* The assembled URL string is returned.
*
* Parameters:
* parts an associative array of strings containing the
* individual parts of a URL.
*
* encode an optional boolean flag selecting whether
* to do percent encoding or not. Default = true.
*
* Return values:
* Returns the assembled URL string. The string is an absolute
* URL if a scheme is supplied, and a relative URL if not. An
* empty string is returned if the $parts array does not contain
* any of the needed values.
*/
function join_url( $parts, $encode=FALSE)
{
if ( $encode )
{
if ( isset( $parts['user'] ) )
$parts['user'] = rawurlencode( $parts['user'] );
if ( isset( $parts['pass'] ) )
$parts['pass'] = rawurlencode( $parts['pass'] );
if ( isset( $parts['host'] ) &&
!preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
$parts['host'] = rawurlencode( $parts['host'] );
if ( !empty( $parts['path'] ) )
$parts['path'] = preg_replace( '!%2F!ui', '/',
rawurlencode( $parts['path'] ) );
if ( isset( $parts['query'] ) )
$parts['query'] = rawurlencode( $parts['query'] );
if ( isset( $parts['fragment'] ) )
$parts['fragment'] = rawurlencode( $parts['fragment'] );
}
$url = '';
if ( !empty( $parts['scheme'] ) )
$url .= $parts['scheme'] . ':';
if ( isset( $parts['host'] ) )
{
$url .= '//';
if ( isset( $parts['user'] ) )
{
$url .= $parts['user'];
if ( isset( $parts['pass'] ) )
$url .= ':' . $parts['pass'];
$url .= '@';
}
if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
$url .= '[' . $parts['host'] . ']'; // IPv6
else
$url .= $parts['host']; // IPv4 or name
if ( isset( $parts['port'] ) )
$url .= ':' . $parts['port'];
if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
$url .= '/';
}
if ( !empty( $parts['path'] ) )
$url .= $parts['path'];
if ( isset( $parts['query'] ) )
$url .= '?' . $parts['query'];
if ( isset( $parts['fragment'] ) )
$url .= '#' . $parts['fragment'];
return $url;
}
/**
* This function encodes URL to form a URL which is properly
* percent encoded to replace disallowed characters.
*
* RFC3986 specifies the allowed characters in the URL as well as
* reserved characters in the URL. This function replaces all the
* disallowed characters in the URL with their repective percent
* encodings. Already encoded characters are not encoded again,
* such as '%20' is not encoded to '%2520'.
*
* Parameters:
* url the url to encode.
*
* Return values:
* Returns the encoded URL string.
*/
function encode_url($url) {
$reserved = array(
":" => '!%3A!ui',
"/" => '!%2F!ui',
"?" => '!%3F!ui',
"#" => '!%23!ui',
"[" => '!%5B!ui',
"]" => '!%5D!ui',
"@" => '!%40!ui',
"!" => '!%21!ui',
"$" => '!%24!ui',
"&" => '!%26!ui',
"'" => '!%27!ui',
"(" => '!%28!ui',
")" => '!%29!ui',
"*" => '!%2A!ui',
"+" => '!%2B!ui',
"," => '!%2C!ui',
";" => '!%3B!ui',
"=" => '!%3D!ui',
"%" => '!%25!ui',
);
$url = rawurlencode($url);
$url = preg_replace(array_values($reserved), array_keys($reserved), $url);
return $url;
}
?>

View File

@ -0,0 +1,35 @@
<?php
function loadUserScriptHeader($file)
{
$result = Array();
if ( is_string($file) )
$file = fopen ( $file, "rt" );
# else assume it's already a readable stream
while ( ( $line = fgets ( $file ) ) !== FALSE )
{
if ( preg_match ( '#//[ \\t]*==/UserScript==#', $line ) )
break;
$matches = Array();
if ( preg_match ( '#^//[ \\t]*(@[a-zA-Z0-9]+)[ \\t]+(.*)$#', $line, $matches ) )
{
$name = $matches[1];
$value = $matches[2];
if ( ! array_key_exists ( $name, $result ) )
{
$result[$name] = $value;
}
}
}
fclose ( $f );
return $result;
}
?>

View File

@ -1,9 +1,7 @@
<h2>IITC Browser Addon</h2>
<?php
if ( $path != "release" )
print "<div class=\"alert alert-block alert-error\"><b>NOTE</b>: the <b>$path</b> build is currently selected. <a href=\"?page=desktop\">Return to the standard build</a>.</div>";
include_once ( "code/desktop-download.php" );
?>
<div class="alert alert-block">
@ -51,16 +49,9 @@ Check your browser documentation for details on installing userscripts.
<h3>Download</h3>
<?php
$iitc_details = loadUserScriptHeader ( "$path/total-conversion-build.user.js" );
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $iitc_details['@version'] );
iitcDesktopDownload ( "release" );
?>
<p>
IITC version <?php print $iitc_version;?>
</p>
<a class="btn btn-large btn-primary" href="<?php print $path;?>/total-conversion-build.user.js">Download</a>
<hr>
@ -71,47 +62,6 @@ Plugins extend/modify the IITC experience. You do <b>not</b> need to install all
a minority of users.
</p>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>ID / Version</th>
<th>Description</th>
<th>Download</th>
</tr>
</thead>
<tbody>
<?php
foreach ( glob ( "$path/plugins/*.user.js" ) as $path )
{
$basename = basename ( $path, ".user.js" );
$details = loadUserScriptHeader ( $path );
print "<tr id=\"plugin-$basename\">\n";
# remove 'IITC Plugin: ' prefix if it's there, for neatness
$name = preg_replace ( '/^IITC plugin: /i', '', $details['@name'] );
# format extended version info in less prominant font
$version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $details['@version'] );
# remove unneeded prefix from description
$description = preg_replace ( '/^\[[^]]*\] */', '', $details['@description'] );
print "<td>$name</td>";
print "<td>$basename<br />$version</td>";
print "<td>$description</td>";
print "<td><a href=\"$path\" class=\"btn btn-small btn-primary\">Download</a></td>";
# print "<a href=\"$path\">".$details['@name']."</a> <i>$name - version ".$details['@version']."</i>: <br/>\n";
# print $details['@description'];
print "</tr>\n";
}
iitcDesktopPluginDownloadTable ( "release" );
?>
</tbody>
</table>

55
website/page/donate.php Normal file
View File

@ -0,0 +1,55 @@
<h2>Donate</h2>
<p>
Thank you for wanting to support IITC. The developers have
<a href="https://github.com/jonatkins/ingress-intel-total-conversion/issues/211">discussed this</a>
and decided that we would prefer you make donations to charity.
</p>
<p>
Our current suggestions are:
</p>
<div class="media">
<a class="pull-left" href="http://www.fsf.org/" style="min-width: 160px">
<img class="media-object" src="assets/img/fsf-logo.png" alt="FSF Logo" title="FSF">
</a>
<div class="media-body">
<h3 class="media-heading">Free Software Foundation</h3>
<p>
The <abbr title="Free Software Foundation">FSF</abbr> is a nonprofit with a worldwide mission to promote
computer user freedom and to defend the rights of all free software users. <small><a href="http://www.fsf.org/about/">[more]</a></small>
</p>
<p>
<a class="btn btn-primary" href="https://my.fsf.org/donate">Donate to the FSF</a>.
</p>
</div>
</div>
<div class="media">
<a class="pull-left" href="https://www.eff.org/" style="min-width: 160px">
<img class="media-object" src="assets/img/eff-logo.png" alt="EFF Logo" title="EFF">
</a>
<div class="media-body">
<h3 class="media-heading">Electronic Frontier Foundation</h3>
<p>
The <abbr title="Electronic Frontier Foundation">EFF</abbr>:
<ul>
<li>Defend free speech for bloggers, journalists, dissidents and ordinary people online.</li>
<li>Protect your privacy by fighting warrantless electronic searches and surveillance.</li>
<li>Advocate for copyright and patent laws that promote rather than chill innovation.</li>
<li>Educate the public about policy and legislation that threaten online freedom around the world, such as SOPA, ACTA, and TPP.</li>
</ul>
<small><a href="https://www.eff.org/about">[more]</a></small>
</p>
<p>
<a class="btn btn-primary" href="https://supporters.eff.org/donate">Donate to the EFF</a>.
</p>
</div>
</div>
</p>
<h3>

View File

@ -36,9 +36,26 @@ IITC Mobile is still in the early stages of development. Many things do not yet
<h3>Download</h3>
<p>
IITC Mobile version 0.3 (with IITC version 0.11.2).
</p>
<?php
<a href="mobile/IITC-Mobile-0.3.apk" class="btn btn-large btn-primary">Download</a>
include_once ( "code/mobile-download.php" );
$apkfile = "mobile/IITC-Mobile-0.3.apk";
if ( file_exists($apkfile) )
{
iitcMobileDownload ( $apkfile );
}
else
{
print "<div class=\"alert alert-error\">Error: <b>$apkfile</b> not found</div>\n";
}
?>
<div class="alert alert-info">
As IITC Mobile is regularly updated, you may want to consider trying the latest
<a href="?page=test#test-mobile">test build</a>.
</div>

81
website/page/test.php Normal file
View File

@ -0,0 +1,81 @@
<h2>Test Builds</h2>
<p>
These test builds are made available for those who would like to try the latest development code without
needing to build it yourself. Automated scripts should update these builds within an hour of a change being
<a href="https://github.com/jonatkins/ingress-intel-total-conversion/commits/master">committed</a> to Github.
</p>
<div class="alert alert-block alert-error">
Test builds are built automatically. They could be <b>broken at any time</b>. If you have any doubts about using
unstable software, please use the standard <a href="?page=desktop">desktop</a> or <a href="?page=mobile">mobile</a>
builds.
</div>
<?php
include_once ( "code/desktop-download.php" );
$path = "test";
if ( $_REQUEST['build'] == 'dev' )
$path = "dev";
if ( $path != "test" )
print "<div class=\"alert alert-block alert-error\"><b>NOTE</b>: A non-standard test build, <b>$path</b>, is currently selected. The notes <b>may not apply!</b> <a href=\"?page=test\">Return to the standard test build</a>.</div>";
$timestamp_file = $path . "/.build-timestamp";
if ( file_exists ( $timestamp_file ) )
{
$build_time = file_get_contents ( $timestamp_file );
print "<div class=\"alert alert-info\">The current test build was built at <b>$build_time</b></div>";
}
else
{
}
?>
<h3 id="test-desktop">Desktop test build</h3>
<?php
iitcDesktopDownload ( $path );
?>
<hr>
<h4>Desktop test plugins</h4>
<?php
iitcDesktopPluginDownloadTable ( $path );
?>
<hr>
<h3 id="test-mobile">Mobile test build</h3>
<?php
include_once ( "code/mobile-download.php" );
$apkfile = "$path/IITC_Mobile-test.apk";
if ( file_exists($apkfile) )
{
iitcMobileDownload ( $apkfile );
}
else
{
print "<div class=\"alert alert-error\">Error: <b>$apkfile</b> not found</div>\n";
}
?>