您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
utils for modifying the DOM and fetching info of a webpage of camamba
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/20132/1023992/camamba-utils.js
// ==UserScript== // @name camamba-utils // @name:de camamba-utils // @namespace dannysaurus.camamba // @version 0.4.1 // @license MIT License // @connect camamba.com // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @require https://gf.qytechs.cn/scripts/22752-object-utils/code/object-utils.js // @require https://gf.qytechs.cn/scripts/20131-html-utils/code/html-utils.js // @description utils for modifying the DOM and fetching info of a webpage of camamba // @description:de utils for modifying the DOM and fetching info of a webpage of camamba // ==/UserScript== var LIB = LIB || {}; /** * @type {{ButtonSmall, ButtonTiny, SelectUsers, pageInfo, User, veewards, UserSearch}} */ LIB.camambaUtils = (function(){ var objUtils = LIB.objectUtils; var htmlUtils = LIB.htmlUtils; /** * Wrapper for 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 * @constructor */ function ButtonSmall(callback, text, id) { if (!(this instanceof ButtonSmall)) { return new ButtonSmall(callback, text, id); } htmlUtils.Button.call(this, 'smallbutton', callback, text, id); } objUtils.extend(ButtonSmall).from(htmlUtils.Button); /** * Wrapper for 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 * @constructor */ function ButtonTiny(callback, text, id) { if (!(this instanceof ButtonTiny)) { return new ButtonTiny(callback, text, id); } htmlUtils.Button.call(this, 'tinybutton', callback, text, id); this.domElement.style = 'margin:0;padding:0;width:auto;'; } objUtils.extend(ButtonTiny).from(htmlUtils.Button); /** * Wrapper for a 'Select' html element for selecting camamba users. * The options will be generated from 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 {boolean} [isShowGivenNames] - <code>true</code> has options shown from the custom name if given instead of the camamba username * @param {function} [callback] - callback function triggered by the events <code>OnChange</code>, <code>OnFocus</code> and <code>KeyUp</code> * @param {string} [id] - The value for the <code>id</code> attribute * @constructor */ function SelectUsers(users, isShowGivenNames, callback, id) { if (!(this instanceof SelectUsers)) { return new SelectUsers(users, isShowGivenNames, callback, id); } htmlUtils.Select.call(this, "smallselect", callback, id); Object.defineProperties(this, { /** * list of users of type {{uid:User}} */ users: { get: function () { return users; }, set: function (value) { users = value; SelectUsers.prototype.updateOptions.call(this); }, enumerable: true, configurable: true }, /** * <code>true</code> shows the custom given names * <code>false</code> shows the camamba names */ isShowGivenNames: { get: function () { return isShowGivenNames; }, set: function (value) { var booleanValue = !!value; if (booleanValue !== keeper.get(_idx).isShowGivenNames) { isShowGivenNames = booleanValue; SelectUsers.prototype.updateOptions.call(this); } }, enumerable: true, configurable: true } }); SelectUsers.prototype.updateOptions.call(this); } SelectUsers.prototype = { constructor: SelectUsers, /** * Recreates the options from the current list of users. */ updateOptions: function() { var sortUsers = []; var remainingUsers = {}; for (var i = 0; i <= this.domElement.length - 1; i++) { var userInSelect = this.domElement[i].user; if (userInSelect) { if (!this.users[userInSelect.uid]) { // deleted users this.domElement.remove(i); } else { // remaining users remainingUsers[userInSelect.uid] = true; sortUsers.push({user: userInSelect, selected: this.domElement[i].selected}); } } } Object.keys(this.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 */ this.domElement.add(document.createElement('OPTION')); } }, this); this.domElement.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) { this.domElement[i].text = opt.user[optionTextProp]; this.domElement[i].value = opt.user.uid; this.domElement[i].user = opt.user; this.domElement[i].selected = opt.selected; }, this); // refresh select this.domElement.value = this.domElement.options[this.domElement.selectedIndex].value; }, /** * Returns the user of the selected option * @returns {User} The current selected user */ getSelectedUser: function() { return this.domElement.options[this.domElement.selectedIndex].user; } }; var pageInfo = (function(){ var isInGerman = location.hostname.indexOf("de.camamba.com") >= 0; var urlRoute = (function() { var route = /^\/(.+?)(?:_de)?\.php.*/g.exec(location.pathname); return route && route.length >= 2 ? route[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 to whether English or German forced. * @param {string} url - 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. * @param {boolean} [isResultInGerman] - <code>true</code> results a German-forced url, <code>false</code> results English forced. * @returns {string} The localized url */ var urlLocalized = function (url, queryParamsObj, isResultInGerman) { var localizedUri = url; if (typeof isResultInGerman === "undefined") { isResultInGerman = isInGerman; } if (isResultInGerman && !urlIsGerman(url)) { localizedUri = url .replace("www.camamba.com", "www.de.camamba.com") .replace(".php", "_de.php"); } else if (!isResultInGerman && urlIsGerman(url)) { localizedUri = url .replace("www.de.camamba.com", "www.camamba.com") .replace("_de.php", "php"); } var queryParams = ''; if (queryParamsObj) { var hasParams = url.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; }; return { isInGerman: isInGerman, route: urlRoute, urlLocalized: urlLocalized }; })(); var userDataStore = {}; //uid:userData var userDataDefaultEntry = function() { return { uid: Number.NaN, uname: "", name: "", note: "", age: Number.NaN, gender: "", isPremium: null, isReal: null, isSuper: null, location: { distanceInKm: Number.NaN, gps: "", place: "" }, online: { isOnlineNow: false, lastSeen: new Date("invalid") }, room: { roomId: "", roomName: "" }, lastUpdated: new Date("invalid"), lastChanged: new Date("invalid") }}; /** * Represents a camamba user. * Intitially it tries to load the User from the database with the given uid. * @constructor * @param {string|number} uid - the users camamba uid. */ function User(uid) { if (!(this instanceof User)) { return new User(uid); } var thisUser = this; var backingData = userDataStore[uid] = objUtils.BackingData(this, userDataDefaultEntry(), "uid" + uid, function(oldValue, newValue) { backingData.lastUpdated = Date.now(); if (newValue != oldValue) { backingData.lastChanged = backingData.lastUpdated; thisUser.isDirty = true; } }); backingData.uid = parseInt(uid,10); this.load(); } User.prototype = { /** * Saves or updates the user in the database of this script. * Overwrites an existing entry or creates a new entry if it doesn't already exist. */ save: function() { userDataStore[this.uid].save(); this.isDirty = false; }, /** * Loads the User from the database of this script. * @returns {Boolean} <code>true</code> if the user was found and could be successfully loaded from db */ load: function() { var isSuccess = userDataStore[this.uid].load(); if (isSuccess) { this.isDirty = false; } return isSuccess; }, /** * Removes the User from the database of this script. */ remove: function() { userDataStore[this.uid].remove(); this.isDirty = true; }, /** * 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 VeeSendCallback * @param {Date} timeSend - Time the veeward beeing send */ /** * Tries to send a veeward. * @param {User|number} [user] - The user or uid to whom the veeward may be send * @param {number} [coolDownPeriodSec] - Period in seconds for which no veeward will be send since last sent * @param {VeeSendCallback} [callback] - A function to be called when the veeward got tried to be send. */ Vee.prototype.send = function(user, coolDownPeriodSec, callback) { if (typeof user === "number") { user = new User(user); } else if (!(user instanceof User)) { user = new User(602175); } coolDownPeriodSec = parseInt(coolDownPeriodSec || 0, 10); if (typeof callback !== 'function') { var thisVee = this; 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) { GM_xmlhttpRequest({ method: 'POST', url: '/extras/setdata.php', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onreadystatechange: function() { GM_setValue('autoVeeTimestamp', now); callback(now); }, data: '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, callback, startPageNr, maxPageNr) { var result = {}; if (paramString.length > 1 && paramString.charAt(paramString.length - 1) !== '&') { paramString += "&"; } startPageNr = startPageNr || 0; maxPageNr = maxPageNr || Number.POSITIVE_INFINITY; var pageNr = startPageNr; if (pageNr > maxPageNr) { return; } var searchHref = '/search.php?' + paramString; htmlUtils.requestPageAsync(searchHref + '&sortreg=1&page=' + pageNr, function(resp) { console.log(searchHref + 'page=' + pageNr); 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 }, uRegDate = new Date(0); 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|couple)/g.exec(genderAgeSearch); if (uGenderSearch && uGenderSearch.length >= 2) { uGender = uGenderSearch[1]; var uAgeTxt = genderAgeSearch.replace(/\D/g, ""); if (uAgeTxt) { uAge = parseInt(uAgeTxt); } } } } } 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); } } } } var reRegDate = /(\d{2})\.(\d{2})\.(\d{4})\s(\d{2}):(\d{1,2}):(\d{2})/; var roomResult = new RegExp(/Online\snow\sin\s([A-Za-z\s]+?[A-Za-z])/.source + reRegDate.source).exec(table.textContent); if (!roomResult) { // private room roomResult = new RegExp(/Online\snow\sin\s(p\d{0,9})/.source + reRegDate.source).exec(table.textContent); } if (roomResult && roomResult.length >= 2) { uOnline.roomName = roomResult[1]; } var regDateResult = reRegDate.exec(table.textContent); if (regDateResult && regDateResult.length >= 7) { var regDateDay = regDateResult[1]; var regDateMonth = regDateResult[2]; var regDateYear = regDateResult[3]; var regDateHour = regDateResult[4]; var regDateMinute = regDateResult[5]; var regDateSecond = regDateResult[6]; uRegDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond); } 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; } if (uOnline.isOnlineNow) { user.online.isOnlineNow = uOnline.isOnlineNow; } if (uOnline.lastSeen) { user.online.lastSeen = uOnline.lastSeen; } if (uOnline.roomId) { user.online.roomId = uOnline.roomId; } if (uOnline.roomName) { user.online.roomName = uOnline.roomName; } if (uRegDate) { user.regDate = uRegDate; } result[uId] = user; } }, this); // userTable }, this); // elTableNormal, elTableSuper var links = resp.html.getElementsByTagName('a'); pageNr++; var isLastPage = true; for (var i = links.length - 1; i >= links.length - 10; i--) { var linkHref = links[i].getAttribute('href'); if (linkHref && linkHref.toLowerCase().indexOf(searchHref + "page=" + pageNr) >= 0) { if (pageNr <= maxPageNr) { setTimeout(function(onlineUsers) { doSearch(paramString, callback, pageNr, maxPageNr); }, 125); isLastPage = false; } break; } } callback(result, isLastPage); }); }; /** @typedef {{MALE: string, FEMALE: string, ANY: string}} GendersType */ /** * @readonly * @enum {string} * @type {GendersType} */ function ParamsUserSearch(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper) { if (!(this instanceof ParamsUserSearch)) { return new ParamsUserSearch(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper); } var paramsObj = {}; var paramEqualsOneIfSet = function(queryParamName) { return { configurable: true, enumerable: true, get: function() { return paramsObj[queryParamName]; }, set: function(value) { if (value) { paramsObj[queryParamName] = 1; } else { delete paramsObj[queryParamName]; } } } }; Object.defineProperties(this, { nick: { configurable: true, enumerable: true, get: function() { return paramsObj.nick; }, set: function(value) { paramsObj.nick = value || ""; } }, gender: { configurable: true, enumerable: true, get: function() { return paramsObj.gender; }, set: function(value) { paramsObj.gender = value || ParamsUserSearch.prototype.genders.ANY; } }, isOnline: paramEqualsOneIfSet("online"), hasPicture: paramEqualsOneIfSet("picture"), isPremium: paramEqualsOneIfSet("isprem"), isReal: paramEqualsOneIfSet("isreal"), isSuper: paramEqualsOneIfSet("issuper") }); this.nick = nick; this.gender = gender; this.isOnline = isOnline; this.hasPicture = hasPicture; this.isPremium = isPremium; this.isReal = isReal; this.isSuper = isSuper; htmlUtils.Params.call(this, paramsObj); } ParamsUserSearch.prototype.genders = { MALE: "male", FEMALE: "female", COUPLE: "couple", ANY: "any" }; Object.freeze(ParamsUserSearch.prototype.genders); objUtils.extend(ParamsUserSearch).from(htmlUtils.Params); /** * 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 || ParamsUserSearch.prototype.genders.ANY; isOnline |= (typeof isOnline === "undefined"); /** * parameter restricting the results of the search * @type {ParamsUserSearch} */ this.queryObject = new ParamsUserSearch(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper); /** * List with users resulting from the last executed search * @type {{}<string|number,User>[]} */ this.users = {}; }; Constructor.prototype = { constructor: Constructor, /** * Executes the search, runs callback when finished * @param callback the callback function that gets the result as list of users */ execute: function(callback) { var that = this; doSearch(this.queryObject.toString(), function(users, isLastPage){ that.users = users; if (typeof callback === "function") { callback(users, isLastPage); } }, 0); } }; return Constructor; })(); return { ButtonSmall: ButtonSmall, ButtonTiny: ButtonTiny, SelectUsers: SelectUsers, pageInfo: pageInfo, User: User, veewards: veewards, UserSearch: UserSearch }; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址