您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
utils for modifying the DOM and fetching info of a webpage of camamba
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/20132/134620/camamba-utils.js
/** * 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; })();