NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Tiberium Alliances ReplayShare
// @version 0.4.1
// @namespace https://openuserjs.org/users/petui
// @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @author petui
// @description Share combat reports with your friends in other alliances and worlds
// @include http*://prodgame*.alliances.commandandconquer.com/*/index.aspx*
// @updateURL https://openuserjs.org/meta/petui/Tiberium_Alliances_ReplayShare.meta.js
// ==/UserScript==
'use strict';
(function() {
var main = function() {
'use strict';
function createReplayShare() {
console.log('ReplayShare loaded');
var Replay = function() {};
Replay.prototype.id = null;
Replay.prototype.data = null;
/**
* @returns {Boolean}
*/
Replay.prototype.isNew = function() {
return this.id === null;
};
/**
* @returns {String}
*/
Replay.prototype.getId = function() {
return this.id;
};
/**
* @param {Number} id
* @returns {Replay}
*/
Replay.prototype.setId = function(id) {
this.id = id;
return this;
};
/**
* @returns {Object}
*/
Replay.prototype.getData = function() {
return this.data;
};
/**
* @param {Object} data
* @returns {Replay}
*/
Replay.prototype.setData = function(data) {
this.data = data;
return this;
};
/**
* @param {Object} data
* @returns {Boolean}
*/
Replay.prototype.equals = function(data) {
return JSON.stringify(this.getData()) === JSON.stringify(data);
};
qx.Class.define('ReplayShare', {
type: 'singleton',
extend: qx.core.Object,
events: {
lastReplayDataChange: 'qx.event.type.Data'
},
members: {
lastReplayData: null,
window: null,
initialize: function() {
this.initializeHacks();
this.initializeEntryPoints();
},
initializeHacks: function() {
if (ClientLib.Vis.Battleground.Battleground.prototype.LoadCombatDirect === undefined) {
var onSimulateBattleMethodName = ClientLib.API.Battleground.prototype.SimulateBattle.toString()
.match(/"SimulateBattle",\s?\{battleSetup:[a-z]+\},\s?\(new \$I\.[A-Z]{6}\)\.[A-Z]{6}\(this,this\.([A-Z]{6})\),\s?this\);/)[1];
var loadCombatDirectMethodName = ClientLib.API.Battleground.prototype[onSimulateBattleMethodName].toString()
.match(/\$I\.[A-Z]{6}\.[A-Z]{6}\(\)\.[A-Z]{6}\(\)\.([A-Z]{6})\(b\.d\);/)[1];
ClientLib.Vis.Battleground.Battleground.prototype.LoadCombatDirect = ClientLib.Vis.Battleground.Battleground.prototype[loadCombatDirectMethodName];
}
var source = ClientLib.Vis.Battleground.Battleground.prototype.LoadCombatDirect.toString();
var initCombatMethodName = source.match(/this\.([A-Z]{6})\(null,[a-z]\);}$/)[1];
var context = this;
var originalInitCombat = ClientLib.Vis.Battleground.Battleground.prototype[initCombatMethodName];
ClientLib.Vis.Battleground.Battleground.prototype[initCombatMethodName] = function(extra, data) {
originalInitCombat.call(this, extra, data);
context.lastReplayData = data;
context.fireDataEvent('lastReplayDataChange', data);
};
var originalOpenLink = webfrontend.gui.Util.openLink;
webfrontend.gui.Util.openLink = function(url) {
if (!context.handleLink(url)) {
originalOpenLink.apply(this, arguments);
}
};
},
initializeEntryPoints: function() {
var scriptsButton = qx.core.Init.getApplication().getMenuBar().getScriptsButton();
scriptsButton.Add('ReplayShare', 'FactionUI/icons/icn_replay_speedup.png');
var children = scriptsButton.getMenu().getChildren();
var lastChild = children[children.length - 1];
lastChild.addListener('execute', this.openWindow, this);
var shareButton = new qx.ui.form.Button('Share').set({
appearance: 'button-text-small',
toolTipText: 'Open in ReplayShare',
width: 80
});
shareButton.addListener('execute', this.onClickShare, this);
qx.core.Init.getApplication().getReportReplayOverlay().add(shareButton, {
right: 70,
top: 35
});
},
/**
* @param {String} key
* @returns {Object}
*/
getConfig: function(key) {
var config = JSON.parse(localStorage.getItem('ReplayShare')) || {};
return key in config ? config[key] : null;
},
/**
* @param {String} key
* @param {Object} value
*/
setConfig: function(key, value) {
var config = JSON.parse(localStorage.getItem('ReplayShare')) || {};
config[key] = value;
localStorage.setItem('ReplayShare', JSON.stringify(config));
},
/**
* @param {String} url
* @returns {Boolean}
*/
handleLink: function(url) {
var matches = url.match(/^https?:\/\/replayshare\.(?:parseapp\.com|petui\.net)\/([A-Za-z0-9]+)/);
if (matches !== null) {
var id = matches[1];
if (this.getConfig('dontAsk')) {
this.openWindow();
this.window.download(id);
}
else {
var context = this;
var widget = new ReplayShare.ConfirmationWidget(url, function(dontAskAgain) {
context.openWindow();
context.window.download(id);
if (dontAskAgain) {
context.setConfig('dontAsk', true);
}
});
widget.open();
}
return true;
}
return false;
},
openWindow: function() {
if (this.window === null) {
this.window = new ReplayShare.Window(this);
}
this.window.open();
},
onClickShare: function() {
this.openWindow();
this.window.onClickFetchReplayData();
},
/**
* @param {Object} replayData
* @returns {Boolean}
*/
tryPlay: function(replayData) {
qx.core.Init.getApplication().getPlayArea().setView(ClientLib.Data.PlayerAreaViewMode.pavmCombatReplay, -1, 0, 0);
try {
ClientLib.Vis.VisMain.GetInstance().get_Battleground().LoadCombatDirect(replayData);
}
catch (e) {
console.log('ReplayShare::tryPlay', e.toString());
return false;
}
return true;
},
/**
* @returns {Boolean}
*/
hasLastReplayData: function() {
return this.lastReplayData !== null;
},
/**
* @returns {Object}
*/
getLastReplayData: function() {
return this.lastReplayData;
}
}
});
qx.Class.define('ReplayShare.Window', {
extend: qx.ui.window.Window,
construct: function(replayShare) {
qx.ui.window.Window.call(this);
this.replayShare = replayShare;
this.service = new ReplayShare.Service();
this.set({
caption: 'ReplayShare',
icon: 'FactionUI/icons/icn_replay_speedup.png',
contentPaddingTop: 0,
contentPaddingBottom: 2,
contentPaddingRight: 6,
contentPaddingLeft: 6,
showMaximize: false,
showMinimize: false,
allowMaximize: false,
allowMinimize: false,
resizable: true
});
this.getChildControl('icon').set({
scale: true,
width: 20,
height: 12,
alignY: 'middle'
});
this.initializePosition();
this.addListener('move', this.onWindowMove, this);
this.setLayout(new qx.ui.layout.VBox());
this.add(this.errorMessageLabel = new qx.ui.basic.Label().set({
font: 'font_size_13',
textColor: '#e44',
visibility: 'excluded'
}));
var createPlayerGroupBox = function(legend, factionImage, nameLabel, baseLabel, allianceLabel) {
var nameContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(4));
nameContainer.add(factionImage.set({
width: 18,
height: 18,
scale: true
}));
nameContainer.add(nameLabel);
var groupBox = new qx.ui.groupbox.GroupBox(legend);
groupBox.setLayout(new qx.ui.layout.Grid(2, 2)
.setColumnFlex(0, 1)
.setColumnFlex(1, 9)
);
groupBox.add(new qx.ui.basic.Label('Name:').set({ font: 'font_size_13_bold' }), { row: 0, column: 0 });
groupBox.add(nameContainer, { row: 0, column: 1 });
groupBox.add(new qx.ui.basic.Label('Base:').set({ font: 'font_size_13_bold' }), { row: 1, column: 0 });
groupBox.add(baseLabel, { row: 1, column: 1 });
groupBox.add(new qx.ui.basic.Label('Alliance:').set({ font: 'font_size_13_bold' }), { row: 2, column: 0 });
groupBox.add(allianceLabel, { row: 2, column: 1 });
return groupBox;
};
var detailsContainer = new qx.ui.container.Composite(new qx.ui.layout.Flow()).set({
font: 'font_size_13',
textColor: '#111'
});
this.add(detailsContainer);
detailsContainer.add(createPlayerGroupBox('Attacker',
this.attackerFactionImage = new qx.ui.basic.Image(),
this.attackerNameLabel = new qx.ui.basic.Label(),
this.attackerBaseLabel = new qx.ui.basic.Label(),
this.attackerAllianceLabel = new qx.ui.basic.Label()
).set({ width: 290 }));
detailsContainer.add(createPlayerGroupBox('Defender',
this.defenderFactionImage = new qx.ui.basic.Image(),
this.defenderNameLabel = new qx.ui.basic.Label(),
this.defenderBaseLabel = new qx.ui.basic.Label(),
this.defenderAllianceLabel = new qx.ui.basic.Label()
).set({ width: 290 }));
this.add(this.timeOfAttackLabel = new qx.ui.basic.Label().set({
alignX: 'right',
textColor: '#aaa'
}));
var controlsContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(4)).set({
marginBottom: 4,
marginLeft: 2,
marginTop: 4
});
this.add(controlsContainer);
this.fetchReplayDataButton = new qx.ui.form.Button('Fetch').set({
enabled: this.replayShare.hasLastReplayData(),
toolTipText: 'Fetch most recently viewed replay or simulation'
});
this.fetchReplayDataButton.addListener('execute', this.onClickFetchReplayData, this);
controlsContainer.add(this.fetchReplayDataButton, { flex: 1 });
if (!this.replayShare.hasLastReplayData()) {
this.replayShare.addListenerOnce('lastReplayDataChange', this.onLastReplayDataChanged, this);
}
this.watchReplayButton = new qx.ui.form.Button('Play').set({
enabled: false,
toolTipText: 'Watch loaded replay'
});
this.watchReplayButton.addListener('execute', this.onClickWatchReplay, this);
controlsContainer.add(this.watchReplayButton, { flex: 1 });
this.uploadButton = new qx.ui.form.Button('Get link').set({
enabled: false,
toolTipText: 'Get share link for loaded replay'
});
this.uploadButton.addListener('execute', this.onClickUpload, this);
controlsContainer.add(this.uploadButton, { flex: 1 });
},
statics: {
DefaultWidth: 300,
DefaultHeight: null
},
members: {
replayShare: null,
service: null,
sharePopup: null,
errorMessageLabel: null,
attackerFactionImage: null,
attackerNameLabel: null,
attackerBaseLabel: null,
attackerAllianceLabel: null,
defenderFactionImage: null,
defenderNameLabel: null,
defenderBaseLabel: null,
defenderAllianceLabel: null,
timeOfAttackLabel: null,
fetchReplayDataButton: null,
watchReplayButton: null,
uploadButton: null,
replay: null,
initializePosition: function() {
var bounds = this.replayShare.getConfig('bounds');
if (bounds === null) {
var baseNavBarX = qx.core.Init.getApplication().getBaseNavigationBar().getLayoutParent().getBounds().left;
bounds = {
left: baseNavBarX - ReplayShare.Window.DefaultWidth - 16,
top: 75,
width: ReplayShare.Window.DefaultWidth,
height: ReplayShare.Window.DefaultHeight
};
}
this.moveTo(bounds.left, bounds.top);
this.setWidth(bounds.width);
this.setHeight(bounds.height);
},
/**
* @param {qx.event.type.Data} event
*/
onWindowMove: function(event) {
this.replayShare.setConfig('bounds', event.getData());
},
onLastReplayDataChanged: function() {
this.fetchReplayDataButton.setEnabled(true);
},
onClickFetchReplayData: function() {
var replayData = this.replayShare.getLastReplayData();
replayData = JSON.parse(JSON.stringify(replayData)); // clone
if (this.replay === null || !this.replay.equals(replayData)) {
this.setReplay(new Replay().setData(replayData));
}
},
onClickWatchReplay: function() {
var replayData = this.replay.getData();
if (!this.replayShare.tryPlay(replayData)) {
this.errorMessageLabel.setValue('Error: Invalid replay data');
this.errorMessageLabel.show();
}
else {
this.errorMessageLabel.exclude();
}
},
onClickUpload: function() {
this.openSharePopup();
if (this.replay.isNew()) {
var context = this;
this.service.save(this.replay, {
success: function(replay) {
context.sharePopup.setLinkURL(context.formatLinkUrl(replay.getId()));
},
error: function(replay, error) {
context.sharePopup.setError(error.message);
}
});
}
else {
this.sharePopup.setLinkURL(this.formatLinkUrl(this.replay.getId()));
}
},
/**
* @param {String} id
* @returns {String}
*/
formatLinkUrl: function scope(id) {
if (scope.baseUrl === undefined) {
// Start serving links to new host on 2016-11-01 12:00:00 UTC
scope.baseUrl = Date.now() < Date.UTC(2016, 11, 1, 12, 0, 0)
? 'https://replayshare.parseapp.com/'
: 'https://replayshare.petui.net/';
}
return scope.baseUrl + id;
},
/**
* @param {Object} replayData
*/
setDetailsFromReplayData: function(replayData) {
var isForgottenAttacker = replayData.af !== ClientLib.Base.EFactionType.GDIFaction && replayData.af !== ClientLib.Base.EFactionType.NODFaction;
this.attackerFactionImage.setSource(phe.cnc.gui.util.Images.getFactionIcon(replayData.af));
this.attackerFactionImage.show();
this.attackerNameLabel.setValue(isForgottenAttacker ? this.tr('tnf:mutants') : replayData.apn);
this.attackerBaseLabel.setValue(this.getReplayAttackerBaseName(replayData));
this.attackerAllianceLabel.setValue(isForgottenAttacker ? this.tr('tnf:mutants') : (replayData.aan || '-'));
var isForgottenDefender = replayData.df !== ClientLib.Base.EFactionType.GDIFaction && replayData.df !== ClientLib.Base.EFactionType.NODFaction;
this.defenderFactionImage.setSource(phe.cnc.gui.util.Images.getFactionIcon(isForgottenDefender ? ClientLib.Base.EFactionType.FORFaction : replayData.df));
this.defenderFactionImage.show();
this.defenderNameLabel.setValue(isForgottenDefender ? this.tr('tnf:mutants') : replayData.dpn);
this.defenderBaseLabel.setValue(this.getReplayDefenderBaseName(replayData));
this.defenderAllianceLabel.setValue(isForgottenDefender ? this.tr('tnf:mutants') : (replayData.dan || '-'));
this.timeOfAttackLabel.setValue(phe.cnc.Util.getDateTimeString(new Date(replayData.toa)));
},
/**
* @param {Object} replayData
* @returns {String}
*/
getReplayAttackerBaseName: function(replayData) {
var attackerBaseName;
switch (replayData.af) {
case ClientLib.Base.EFactionType.FORFaction:
case ClientLib.Base.EFactionType.NPCBase:
case ClientLib.Base.EFactionType.NPCCamp:
case ClientLib.Base.EFactionType.NPCOutpost:
case ClientLib.Base.EFactionType.NPCFortress:
case ClientLib.Base.EFactionType.NPCEvent:
attackerBaseName = this.tr(replayData.an) + ' (' + replayData.abl + ')';
break;
default:
attackerBaseName = replayData.an;
}
return attackerBaseName;
},
/**
* @param {Object} replayData
* @returns {String}
*/
getReplayDefenderBaseName: function(replayData) {
var defenderBaseName;
switch (replayData.df) {
case ClientLib.Base.EFactionType.FORFaction:
case ClientLib.Base.EFactionType.NPCBase:
case ClientLib.Base.EFactionType.NPCCamp:
case ClientLib.Base.EFactionType.NPCOutpost:
case ClientLib.Base.EFactionType.NPCFortress:
case ClientLib.Base.EFactionType.NPCEvent:
var defenderPlayerId = replayData.dpi;
var type;
switch (Math.abs(defenderPlayerId) % 100) {
case ClientLib.Data.Reports.ENPCCampType.Beginner:
case ClientLib.Data.Reports.ENPCCampType.Random:
type = 'tnf:mutants camp';
break;
case ClientLib.Data.Reports.ENPCCampType.Cluster:
type = 'tnf:mutants outpost';
break;
case ClientLib.Data.Reports.ENPCCampType.Fortress:
type = 'tnf:centerhub short';
break;
case ClientLib.Data.Reports.ENPCCampType.Event:
type = 'tnf:event camp';
break;
default:
type = 'tnf:mutants base';
}
defenderBaseName = this.tr(type) + ' (' + Math.floor(Math.abs(defenderPlayerId) / 100) + ')';
break;
default:
defenderBaseName = replayData.dn;
}
return defenderBaseName;
},
/**
* @param {String} id
*/
download: function(id) {
this.errorMessageLabel.exclude();
this.resetFields('Loading...');
var context = this;
this.service.get(id, {
success: function(replay) {
context.setReplay(replay);
},
error: function(replay, error) {
context.errorMessageLabel.setValue('Error: ' + error.message);
context.errorMessageLabel.show();
if (context.replay !== null) {
context.setDetailsFromReplayData(context.replay.getData());
}
else {
context.resetFields(null);
}
}
});
},
/**
* @param {Replay} replay
*/
setReplay: function(replay) {
this.replay = replay;
this.setDetailsFromReplayData(replay.getData());
this.watchReplayButton.setEnabled(true);
this.uploadButton.setEnabled(true);
},
openSharePopup: function() {
if (this.sharePopup === null) {
var bounds = this.getBounds();
this.sharePopup = new ReplayShare.Window.ShareLink();
this.sharePopup.moveTo(
bounds.left - (this.sharePopup.getWidth() - bounds.width) / 2,
bounds.top - (this.sharePopup.getHeight() - bounds.height) / 2
);
}
this.sharePopup.open();
},
/**
* @param {String} label
*/
resetFields: function(label) {
this.attackerFactionImage.exclude();
this.attackerFactionImage.setSource(null);
this.attackerNameLabel.setValue(label);
this.attackerBaseLabel.setValue(label);
this.attackerAllianceLabel.setValue(label);
this.defenderFactionImage.exclude();
this.defenderFactionImage.setSource(null);
this.defenderNameLabel.setValue(label);
this.defenderBaseLabel.setValue(label);
this.defenderAllianceLabel.setValue(label);
this.timeOfAttackLabel.setValue(label);
}
}
});
qx.Class.define('ReplayShare.Window.ShareLink', {
extend: qx.ui.window.Window,
construct: function() {
qx.ui.window.Window.call(this);
this.set({
caption: 'Share link',
allowMaximize: false,
allowMinimize: false,
showMinimize: false,
showMaximize: false,
showClose: true,
resizable: false,
padding: 1,
textColor: '#aaa',
width: 378,
height: 98
});
this.setLayout(new qx.ui.layout.VBox());
this.add(new qx.ui.basic.Label('Copy the link to share this replay with others'));
this.add(this.linkField = new qx.ui.form.TextField().set({
readOnly: true,
focusable: true,
placeholder: 'Loading...'
}));
this.add(this.errorMessageLabel = new qx.ui.basic.Label().set({
textColor: '#e44',
visibility: 'excluded'
}));
this.linkField.addListener('click', this.linkField.selectAllText, this.linkField);
this.addListener('changeActive', this.onChangeActive, this);
},
members: {
linkField: null,
errorMessageLabel: null,
/**
* @param {qx.event.type.Data} event
*/
onChangeActive: function(event) {
if (!event.getData()) {
this.close();
}
},
open: function() {
this.linkField.setValue(null);
this.errorMessageLabel.exclude();
qx.ui.window.Window.prototype.open.call(this);
},
/**
* @param {String} url
*/
setLinkURL: function(url) {
this.linkField.setValue('[url]' + url + '[/url]');
new qx.util.DeferredCall(function() {
this.linkField.focus();
this.linkField.selectAllText();
}, this).schedule();
},
/**
* @param {String} error
*/
setError: function(error) {
this.errorMessageLabel.setValue(error);
this.errorMessageLabel.show();
}
}
});
qx.Class.define('ReplayShare.ConfirmationWidget', {
extend: webfrontend.gui.CustomWindow,
construct: function(url, callback) {
webfrontend.gui.CustomWindow.call(this);
this.callback = callback;
this.url = url;
this.set({
caption: 'Open link',
allowMaximize: false,
allowMinimize: false,
showMaximize: false,
showMinimize: false,
showClose: false,
resizable: false,
modal: true
});
this.setLayout(new qx.ui.layout.VBox(10));
this.addListenerOnce('resize', this.center, this);
this.add(new qx.ui.basic.Label('Would you like to open this link with ReplayShare?').set({
rich: true,
maxWidth: 360,
wrap: true,
textColor: 'white'
}));
var buttonContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({
alignX: 'right'
}));
var yesDontAskButton = new webfrontend.ui.SoundButton('Yes and don\'t ask again');
yesDontAskButton.addListener('execute', this.openReplayShareAndDontAsk, this);
var yesButton = new webfrontend.ui.SoundButton('Yes');
yesButton.addListener('execute', this.openReplayShare, this);
var noButton = new webfrontend.ui.SoundButton('No');
noButton.addListener('execute', this.openExternal, this);
var cancelButton = new webfrontend.ui.SoundButton('Cancel');
cancelButton.addListener('execute', this.close, this);
buttonContainer.add(yesDontAskButton);
buttonContainer.add(yesButton);
buttonContainer.add(noButton);
buttonContainer.add(cancelButton);
this.add(buttonContainer);
},
members: {
callback: null,
url: null,
openExternal: function() {
this.close();
qx.core.Init.getApplication().showExternal(this.url);
},
openReplayShareAndDontAsk: function() {
this.close();
this.callback.call(null, true);
},
openReplayShare: function() {
this.close();
this.callback.call(null, false);
}
}
});
qx.Class.define('ReplayShare.Service', {
extend: qx.core.Object,
members: {
/**
* @param {String} id
* @param {Object} options
*/
get: function(id, options) {
var requestOptions = {
method: 'GET',
accept: 'application/json'
};
if (options.success !== undefined) {
requestOptions.success = function(data) {
options.success(new Replay()
.setId(id)
.setData(data.data)
);
};
}
if (options.error !== undefined) {
requestOptions.error = function(error) {
options.error(null, { message: error });
};
}
this._sendRequest('https://replayshare.petui.net/' + id, requestOptions);
},
/**
* @param {Replay} replay
* @param {Object} options
*/
save: function(replay, options) {
var requestOptions = {
method: 'PUT',
accept: 'application/json',
contentType: 'application/json',
data: JSON.stringify({
data: replay.getData(),
rev: PerforceChangelist || null
})
};
if (options.success !== undefined) {
requestOptions.success = function(data) {
options.success(replay.setId(data.id));
};
}
if (options.error !== undefined) {
requestOptions.error = function(error) {
options.error(replay, { message: error });
};
}
this._sendRequest('https://replayshare.petui.net', requestOptions);
},
/**
* @param {String} url
* @param {Object} options
*/
_sendRequest: function(url, options) {
var request = new qx.io.request.Xhr(url);
request.setMethod(options.method || 'GET');
request.setTimeout(options.timeout || 10000);
if (options.accept !== undefined) {
request.setAccept(options.accept);
}
if (options.contentType !== undefined) {
request.setRequestHeader('Content-Type', options.contentType);
}
if (options.data !== undefined) {
request.setRequestData(options.data);
}
if (options.success !== undefined) {
request.addListener('success', function(event) {
options.success(event.getTarget().getResponse());
}, this);
}
if (options.error !== undefined) {
request.addListener('error', function(event) {
options.error('Unknown error');
});
request.addListener('statusError', function(event) {
var response = event.getTarget().getResponse();
if (response.error !== undefined) {
options.error(response.error.message || response.error.statusText);
}
else {
options.error('Unknown server error');
}
});
request.addListener('timeout', function(event) {
options.error('Request timed out');
});
}
request.send();
}
}
});
}
function waitForGame() {
try {
if (typeof qx !== 'undefined' && qx.core.Init.getApplication() && qx.core.Init.getApplication().initDone) {
createReplayShare();
ReplayShare.getInstance().initialize();
}
else {
setTimeout(waitForGame, 1000);
}
}
catch (e) {
console.log('ReplayShare: ', e.toString());
}
}
setTimeout(waitForGame, 1000);
};
var script = document.createElement('script');
script.innerHTML = '(' + main.toString() + ')();';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
})();