/**
* Extension for HtmlFactory
*/
(function() {
/**
* Creates a 'button' Html Element.
* The initial class attribute is <code>class='smallbutton'</code>.
* @param {function} [callback] - The callback function for the <code>onClick<code> event
* @param {string} [text] - The content text of the element (text shown on the button)
* @param {string} [id] - The value for the <code>id</code> attribute
* @returns {ElType} The 'button' HTML element
*/
var createButtonSmall = function(callback, text, id) {
return HtmlFactory.newButton('smallbutton', callback, text, id);
};
/**
* Creates a 'button' Html Element.
* The initial class attribute is <code>class='tinybutton'</code>.
* @param {function} [callback] - The callback function for the <code>onClick<code> event
* @param {string} [text] - The content text of the element (text shown on the button)
* @param {string} [id] - The value for the <code>id</code> attribute
* @returns {ElType} The 'button' HTML element
*/
var createButtonTiny = function ButtonTiny(callback, text, id) {
var elBtn = HtmlFactory.newButton('tinybutton', callback, text, id);
elBtn.style = 'margin:0;padding:0;width:auto;';
return elBtn;
};
/**
* Creates a 'Select' HTML element for selecting camamba users.
* The options will be generated by the list of users.
* The initial class attribute is <code>class='smallselect'</code>.
* @param {Object<string, User>[]} users - The list of users shown as options
* @param {string} [id] - The value for the <code>id</code> attribute
* @param {boolean} [isShowGivenNames] - <code>true</code> has options shown by the custom name if given instead of the camamba username
* @returns {ElType} The 'select' HTML element
*/
var createSelectUsers = function(users, id, isShowGivenNames) {
var elSelect = HtmlFactory.newSelect('smallselect', id);
var _users = users, _isShowGivenNames = isShowGivenNames;
var _onchangeCallback;
/**
* Recreates the options by the current list of users.
*/
var updateOptions = function() {
var sortUsers = [];
var remainingUsers = {};
for (var i = 0; i <= elSelect.length - 1; i++) {
var userInSelect = elSelect[i].user;
if (userInSelect) {
if (!_users[userInSelect.uid]) { // deleted users
elSelect.remove(i);
} else { // remaining users
remainingUsers[userInSelect.uid] = true;
sortUsers.push({ user:userInSelect, selected:elSelect[i].selected });
}
}
}
Object.keys(_users).forEach(function(uid){
if (!remainingUsers[uid]) { // additional users
var user = users[uid];
sortUsers.push({ user:user, selected:false });
/**
* Html 'Option' Child of a Html 'Select' Element that holds a User
* @type {HTMLOptionElement}
* @property {User} user - The User related to the option
*/
elSelect.add(document.createElement('OPTION'));
}
});
elSelect.length = sortUsers.length;
var optionTextProp = _isShowGivenNames ? "name" : "uname";
sortUsers.sort(function (a, b) {
var nameA = a.user[optionTextProp].toLowerCase();
var nameB = b.user[optionTextProp].toLowerCase();
if (nameA < nameB) { return -1; }
if (nameA > nameB) { return 1; }
return 0;
});
sortUsers.forEach(function (opt, i) {
elSelect[i].text = opt.user[optionTextProp];
elSelect[i].value = opt.user.uid;
elSelect[i].user = opt.user;
elSelect[i].selected = opt.selected;
});
// refresh select
elSelect.value = elSelect.options[elSelect.selectedIndex].value;
};
/**
* Returns the user of the selected option
* @returns {User} The current selected user
*/
var getSelectedUser = function() {
return elSelect.options[elSelect.selectedIndex].user;
};
/**
* Sets a callback function triggered by the events <code>OnChange</code>, <code>OnKeyUp</code> and <code>OnFocus</code>.
* Removes any former callback function registered to these events.
* @param {function} callback
*/
var setOnChangeKeyUpFocus = function(callback) {
if (_onchangeCallback) {
elSelect.removeEventListener("focus", _onchangeCallback);
elSelect.removeEventListener("change", _onchangeCallback);
elSelect.removeEventListener("keyup", _onchangeCallback);
}
if (typeof callback === 'function') {
_onchangeCallback = callback;
elSelect.addEventListener("focus", callback);
elSelect.addEventListener("change", callback);
elSelect.addEventListener("keyup", callback);
}
};
Object.defineProperties(elSelect, {
/**
* list of users of type {{uid:User}}
*/
users : {
get : function() { return _users },
set : function(value) {
_users = value;
updateOptions();
}
},
/**
* <code>true</code> shows the custom given names
* <code>false</code> shows the camamba names
*/
isShowGivenNames : {
get : function() { return _isShowGivenNames },
set : function(value) {
if (value != _isShowGivenNames) {
_isShowGivenNames = value;
updateOptions();
}
}
},
refresh : { value : updateOptions },
selectedUser : { value : getSelectedUser },
setOnChangeKeyUpFocus : { value : setOnChangeKeyUpFocus }
});
updateOptions();
return elSelect;
};
Object.defineProperties(HtmlFactory, {
newButtonSmall : { value: createButtonSmall },
newButtonTiny : { value: createButtonTiny },
newSelectUsers : { value: createSelectUsers }
});
})();
/**
* Extension for Page
*/
(function() {
var isGerman = window.location.hostname.indexOf("de.camamba.com") >= 0;
var urlRoute = /^\/(.+?)(?:_de)?\.php.*/g.exec(location.pathname)[1];
/**
* Verifies an url, if it loads the German version of camamba.
* @param {string} url The url for a camamba Page.
* @returns {boolean} <code>true</code> if the url will request a camamba Page in German.
*/
var urlIsGerman = function(url) {
return (url.indexOf('www.de.camamba.com') >= 0);
};
/**
* Transforms the url of a camamba Page wether to request that Page in English or German, depending the language of the current Page.
* @param {string} uri - The url for a cammaba Page.
* @param {Object.<string,string>[]} [queryParamsObj] - A key-value Object for additional query parameter to be attached to the url.
* @returns {string} The localized url
*/
var uriLocalized = function(uri, queryParamsObj) {
var localizedUri = uri;
if (isGerman && !urlIsGerman(uri)) {
localizedUri = uri
.replace("www.camamba.com", "www.de.camamba.com")
.replace(".php", "_de.php");
} else if (!isGerman && urlIsGerman(uri)) {
localizedUri = uri
.replace("www.de.camamba.com", "www.camamba.com")
.replace("_de.php", "php");
}
var queryParams = '';
if (queryParamsObj) {
var hasParams = uri.indexOf('.php?') >= 1;
Object.keys(queryParamsObj).forEach(function (key) {
var sep = (hasParams ? '&' : '?');
var value = queryParamsObj[key];
queryParams += sep + key + '=' + value;
hasParams = true;
});
}
return localizedUri + queryParams;
};
Object.defineProperties(Page, {
/**
* Indicates the localization of the current camamba Page.
*/
isGerman : { value: isGerman },
/**
* The current path in camamba according to the url.
*/
route : { value: urlRoute },
localizeUri : { value: uriLocalized }
});
})();
/**
* Represents a camamba user.
* Intitially tries to load the User from the database by the given uid.
* @constructor
* @param {string|number} uid Identifies the User with its camaba uid.
*/
function User(uid) {
var _uid = parseInt(uid); // camamba user ID (readonly)
var _key = 'uid' + _uid; // key for storing (readonly)
var _hasChanged = true;
var backingFields = {
uname : "", name : "", note : "",
age : Number.NaN, gender : "",
isPremium : false, isReal : false, isSuper : false,
location : {}, distanceInKm : Number.NaN, gps : "", place : "",
online : {}, isOnlineNow : false, lastSeen : new Date("invalid"),
room : {}, roomId : "", roomName : "",
lastUpdated : Date.now(), lastChanged : Date.now()
};
var setField = function(fieldName, val) {
_lastUpdated = Date.now();
if (backingFields[fieldName] !== val) {
backingFields[fieldName] = val;
_lastChanged = _lastUpdated;
_hasChanged = true;
}
};
var getField = function(fieldName) { return backingFields[fieldName]; };
Object.defineProperties(getField("location"), {
distanceInKm : {
get: function() { return getField("distanceInKm"); },
set: function(val) { setField("distanceInKm", val); }
},
gps : {
get: function() { return getField("gps"); },
set: function(val) { setField("gps", val); }
},
place : {
get: function() { return getField("place"); },
set: function(val) { setField("place", val); }
}
});
Object.defineProperties(getField("online"), {
isOnlineNow : {
get: function() { return getField("isOnlineNow"); },
set: function(val) { setField("isOnlineNow", val); }
},
lastSeen : {
get: function() { return getField("lastSeen"); },
set: function(val) { setField("lastSeen", val); }
},
roomId: {
get: function() { return getField("roomId"); },
set: function(val) { setField("roomId", val); }
},
roomName: {
get: function() { return getField("roomName"); },
set: function(val) { setField("roomName", val); }
}
});
/**
* @name User#uid
* @type number
* @readonly
*/
/**
* @name User#key
* @type String
* @readonly
*/
/**
* @name User#hasChanged
* @type Boolean
* @readonly
*/
/**
* @name User#note
* @type String
*/
Object.defineProperties(this, {
uid : { value : _uid, writable : false },
key : { value : _key, writable : false },
hasChanged : { get : function() { return _hasChanged; } },
lastChanged : { get : function() { return getField("lastChanged"); } },
lastUpdated : { get : function() { return getField("lastUpdated"); } },
uname : {
get : function() { return getField("uname"); },
set : function(val) { setField("uname", val); }
},
name : {
get : function() { return getField("name") || getField("uname") || _uid.toString(); },
set : function(val) { setField("name", val); }
},
note : {
get : function() { return getField("note"); },
set : function(val) { setField("note", val); }
},
age : {
get : function() { return getField("age"); },
set : function(val) { setField("age", val); }
},
gender : {
get: function() { return getField("gender"); },
set: function(val) { setField("gender", val); }
},
isPremium : {
get: function() { return getField("isPremium"); },
set: function(val) { setField("isPremium", val); }
},
isReal : {
get: function() { return getField("isReal"); },
set: function(val) { setField("isReal", val); }
},
isSuper : {
get: function() { return getField("isSuper"); },
set: function(val) { setField("isSuper", val); }
},
location : { get: function() { return getField("location"); } },
online : { get: function() { return getField("online"); } }
});
/**
* Saves or updates the user in the database of this script.
* Overwrites an existing entry or creates a new entry if it doesn't alread exist.
*/
this.save = function() {
User.prototype.save.call(this);
_hasChanged = false;
};
/**
* Loads the User from the database of this script.
* @returns {Boolean} <code>true</code> if the user was found and could be sucessfully loaded from db
*/
this.load = function() {
var isSuccess = User.prototype.load.call(this);
if (isSuccess) {_hasChanged = false; }
return isSuccess;
};
/**
* Removes the User from the database of this script.
*/
this.remove = function () {
User.prototype.remove.call(this);
_hasChanged = true;
};
this.load();
}
User.prototype = {
constructor : User,
save : function() {
if (this.hasChanged) {
GM_setValue("uid" + this.uid, JSON.stringify({
uname : this.uname,
name : this.name,
note : this.note,
age : this.age,
gender : this.gender,
isPremium : this.isPremium,
isReal : this.isReal,
isSuper : this.isSuper,
location : this.location,
online : this.online,
lastChanged : this.lastChanged,
lastUpdated : this.lastUpdated
}));
}
},
load : function() {
var isScuccess = false;
var loadedString = GM_getValue("uid" + this.uid);
if (loadedString) {
var loadedObj = JSON.parse(loadedString);
var uname = loadedObj.uname;
var name = loadedObj.name;
var note = loadedObj.note;
if (uname !== undefined && name !== undefined && note !== undefined){
this.uname = uname;
this.name = name;
this.note = note;
isScuccess = true;
}
}
return isScuccess;
},
remove : function() {
GM_deleteValue("uid" + this.uid);
},
/**
* Gets all users stored from the database determined for this Script.
* @returns {{}<string|number,User>[]} List with all Stored Users
*/
loadAllUsers : function() {
var users = {};
var storedKeys = GM_listValues();
for (var i = 0; i <= storedKeys.length - 1; i++) {
var key = storedKeys[i].toString();
if (key.indexOf('uid') === 0) {
var uid = key.substr(3);
users[uid] = new User(uid);
}
}
return users;
},
/**
* Has the browser open the profile Page of this user.
* @param {boolean} [asNewTab=false]
* <code>true</code>, Page is opened in a new tab.
* <code>false</code>, replaces the current Page.
*/
openProfilePage : function(asNewTab) {
var profPageLocPath = location.protocol + '//www.camamba.com/profile_view.php';
var queryParamsObj = { uid : this.uid, m : 'start' };
var uri = Page.localizeUri(profPageLocPath, queryParamsObj);
var target = asNewTab ? '_blank' : '_self';
window.open(uri, target);
}
};
/**
* Represents the veewards
* @type {{lol, drama, banHammer, cheese}}
*/
var veewards = (function() {
function Vee(idx, name) {
this.index = idx;
this.name = name;
}
/**
* This callback is displayed as part of the Requester class.
* @callback Vee~sendCallback
* @param {Date} timeSend - Time the veeward beeing send
*/
/**
* Tries to send a veeward.
* @param {User} [user] - The user to whom the veeward may be send
* @param {number} [coolDownPeriodSec] - Period in seconds for which no veeward will be send since last sent
* @param {Vee~sendCallback} [callback] - A function to be called when the veeward got tried to be send.
*/
Vee.prototype.send = function (user, coolDownPeriodSec, callback) {
if (!(user && user instanceof User)) { user = new User(602175); }
coolDownPeriodSec = parseInt(coolDownPeriodSec||0, 10);
if (typeof callback !== 'function') {
callback = function(now) {
console.info(new Date(now * 1000), 'try to veeward ', user.name, ' with ', thisVee.name);
};
}
var now = function() {
var now = Date.now ? Date.now() : new Date().getTime();
return Math.floor(now / 1000);
}();
var lastVeeTime = GM_getValue('autoVeeTimestamp', 0);
if (lastVeeTime > now || lastVeeTime + coolDownPeriodSec < now) {
var thisVee = this, vhttp;
try {
vhttp = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
vhttp = new ActiveXObject('Microsoft.XMLHTTP');
} catch (E) {
vhttp = false;
}
}
if (!vhttp && typeof XMLHttpRequest != 'undefined') {
try {
vhttp = new XMLHttpRequest();
} catch (e) {
vhttp = false;
}
}
vhttp.open('POST', '/extras/setdata.php', true);
vhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
vhttp.onreadystatechange = function() {
GM_setValue('autoVeeTimestamp', now);
callback(now);
};
vhttp.send('sendvee=' + this.index + '&to=' + user.uid);
}
};
return {
lol: new Vee(1, 'LoL'),
drama: new Vee(2, 'Drama'),
banHammer: new Vee(3, 'Ban Hammer'),
cheese: new Vee(4, 'Cheese')
};
})();
/**
* Represents a search using the page "/search.php".
* @constructor
*/
var UserSearch = (function() {
var doSearch = function (paramString, pageNr) {
pageNr = pageNr || 0;
var searchHref = '/search.php' + (paramString ? paramString + '&' : '?') + 'page=' + pageNr;
var onlineUsers = {};
Page.getAsync(searchHref, function(resp) {
var elTablesNormal = resp.html.getElementsByClassName('searchNormal');
var elTablesSuper = resp.html.getElementsByClassName('searchSuper');
var timeStampInMs = Date.now();
[elTablesNormal, elTablesSuper].forEach(function (userTables, index) {
var uIsSuper = index === 1;
[].forEach.call(userTables, function(table) {
var user, uId, uName, uGender, uAge, uIsPremium, uIsReal,
uLocation = {},
uOnline = { isOnlineNow : true, lastSeen : timeStampInMs };
var links = table.getElementsByTagName('a');
for (var i = 0; i <= links.length -1; i++) {
var link = links[i];
if (!uId) {
var uidSearch = /javascript:sendMail\('(.+)'\)/g.exec(link.href);
if (uidSearch && uidSearch.length >= 2) {
uId = uidSearch[1];
uIsPremium = uIsReal = false;
if (link.nextSibling && link.nextSibling.nextSibling && link.nextSibling.nextSibling.nextSibling) {
var elPremiumReal = link.nextSibling.nextSibling.nextSibling;
if (elPremiumReal) {
if (elPremiumReal.getAttribute('href') === '/premium.php') {
uIsPremium = true;
if (elPremiumReal.nextSibling && elPremiumReal.nextSibling.nextSibling) {
elPremiumReal = elPremiumReal.nextSibling.nextSibling;
}
}
}
if (elPremiumReal && elPremiumReal.getAttribute('src') === '/gfx/real.png') {
uIsReal = true;
}
}
}
}
if (!uName || !uGender) {
var unameSearch = /javascript:openProfile\('(.+)'\)/g.exec(link.href);
if (unameSearch && unameSearch.length >= 2) {
uName = unameSearch[1];
if (link.nextSibling && link.nextSibling.nextSibling) {
var genderAgeSearch = link.nextSibling.nextSibling.textContent;
var uGenderSearch = /(male|female)/g.exec(genderAgeSearch);
if (uGenderSearch && uGenderSearch.length >= 2) {
uGender = uGenderSearch[1];
var uAgeTxt = genderAgeSearch.replace(/\D/g,"");
if (uAgeTxt) {
uAge = parseInt(uAgeTxt);
}
}
}
}
}
if (!uOnline.roomId) {
var onlineSearch = /javascript:openChat\('(.+)'\)/g.exec(link.href);
if (onlineSearch && onlineSearch.length >= 2) {
uOnline.roomId = onlineSearch[1];
uOnline.roomName = link.innerHTML;
}
}
if (!uLocation.gps) {
var locationSearch = /javascript:openMap\((.+)\)/g.exec(link.href);
if (locationSearch && locationSearch.length >= 2) {
uLocation = {
gps : locationSearch[1],
place : link.innerHTML
};
var distanceTxt = link.nextSibling.textContent.replace(/\D/g,"");
if (distanceTxt) {
uLocation.distanceInKm = parseInt(distanceTxt);
}
}
}
}
if (uId) {
user = new User(uId);
if (uAge) { user.age = uAge; }
if (uGender) { user.gender = uGender; }
user.isPremium = uIsPremium;
user.isReal = uIsReal;
user.isSuper = uIsSuper;
if (uLocation.distanceInKm !== undefined) {
user.location.distanceInKm = uLocation.distanceInKm;
}
if (uLocation.gps) { user.location.gps = uLocation.gps; }
if (uLocation.place) { user.location.place = uLocation.place; }
if (uName) { user.uname = uName; }
user.online.isOnlineNow = uOnline.isOnlineNow;
user.online.lastSeen = uOnline.lastSeen;
user.online.roomId = uOnline.roomId;
user.online.roomName = uOnline.roomName;
onlineUsers[uId] = user;
}
});
});
pageNr++;
var links = resp.html.getElementsByTagName('a');
for (var i = links.length -1; i >= 0; i--) {
var linkHref = links[i].getAttribute('href');
if (linkHref && linkHref.toLowerCase().indexOf(searchHref) >= 0) {
console.log('Link:', linkHref);
setTimeout(function(){
doSearch(paramString, pageNr);
}, 750);
break;
}
}
});
return onlineUsers;
};
/** @constructor */
var Params = function(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper) {
this.nick = nick;
this.gender = gender;
this.isOnline = isOnline;
this.hasPicture = hasPicture;
this.isPremium = isPremium;
this.isReal = isReal;
this.isSuper = isSuper;
};
Params.prototype.toString = function() {
var params = [];
if (typeof this.nick !== "undefined") { params.push("nick=" + this.nick); }
if (typeof this.gender !== "undefined") { params.push("gender=" + this.gender || "any"); }
if (typeof this.isOnline !== "undefined") { params.push("online=" + this.isOnline ? "1" : "0"); }
if (typeof this.hasPicture !== "undefined") { params.push("picture=" + this.hasPicture ? "1" : "0"); }
if (typeof this.isPremium !== "undefined") { params.push("isprem=" + this.isPremium ? "1" : "0"); }
if (typeof this.isReal !== "undefined") { params.push("isreal=" + this.isReal ? "1" : "0"); }
if (typeof this.isSuper !== "undefined") { params.push("issuper=" + this.isSuper ? "1" : "0"); }
var result = "";
params.forEach(function(param){
result += (result.length === 0) ? "?" : "&" + param;
});
return result;
};
/** @typedef {{MALE: string, FEMALE: string, ANY: string}} GendersType */
/**
* @readonly
* @enum {string}
* @type {GendersType}
*/
Params.prototype.genders = { MALE : "male", FEMALE : "female", ANY : "any" };
Object.freeze(Params.prototype.genders);
/**
* Represents a search using the page "/search.php".
* The parameters restrict the search ...
* @param {string} [nick] - to users whose username to start with a given string
* @param {GendersType} [gender=any] - to users with a given gender
* @param {boolean} [isOnline=true] - to users currently online
* @param {boolean} [isReal=false] - to users with a real tag
* @param {boolean} [hasPicture=false] - to users with a picture uploaded to their profile
* @param {boolean} [isPremium=false] - to users with premium
* @param {boolean} [isSuper=false] - to users with super premium
* @constructor
*/
var Constructor = function(nick, gender, isOnline, isReal, hasPicture, isPremium, isSuper) {
gender = gender || Params.prototype.genders.ANY;
isOnline |= (typeof isOnline === "undefined");
/**
* parameter restricting the results of the search
* @type {Params}
*/
this.params = new Params(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper);
/**
* List with users resulting the last executed search
* @type {{}<string|number,User>[]}
*/
this.users = {};
};
Constructor.prototype = {
constructor: Constructor,
/** executes the search */
execute: function() {
this.users = doSearch(this.params.toString());
}
};
return Constructor;
})();