Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/405699/934111/Camamba%20User.js
// ==UserScript==
// @name Camamba User
// @namespace dannysaurus.camamba
//
// @include http://www.camamba.com/*
// @include https://www.camamba.com/*
// @include http://www.de.camamba.com/*
// @include https://www.de.camamba.com/*
//
// @connect camamba.com
// @grant GM_xmlhttpRequest
//
// @require https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js
// @require https://greasyfork.org/scripts/391854-enum/code/Enum.js
//
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
//
// @version 0.1
// @license MIT License
// @description User represents a camamba User, UserSearch represents a search-request for one or many users
// @author Gerrit Höhle
// ==/UserScript==
/* jslint esnext: true */
/* globals HttpRequest, HttpRequestHtml, Enum, $, jQuery */
const User = (() => {
'use strict';
const GENDERS = class GENDER extends Enum {}.init({ ANY: 'any' , FEMALE: 'female' , MALE: 'male', COUPLE: 'couple' });
return class User {
constructor({ uid, nick, age, gender, location, distanceKm, latitudeDec, longitudeDec, isOnline, lastSeen, room, roomId, isReal, isPremium, isSuper, userLevel, timeStamp = new Date() } = {}) {
Object.assign(this, { uid, nick, age, _gender: gender, location, distanceKm, latitudeDec, longitudeDec, isOnline, lastSeen, room, roomId, isReal, isPremium, isSuper, userLevel, timeStamp });
}
get gender() {
return this._gender instanceof GENDERS ? this._gender : GENDERS.ANY;
}
set gender(value) {
if (value instanceof GENDERS) {
this._gender = value;
} else {
this._gender = [ ...GENDERS ].find(gender => gender.toString() === String(value)) || GENDERS.ANY;
}
}
static get GENDERS() {
return GENDERS;
}
};
})();
const UserSearch = (() => {
const CONST = {
URL_SEARCH: 'https://www.camamba.com/search.php',
URL_USER_LEVEL: 'https://www.camamba.com/user_level.php'
};
const UserControllerInternal = (() => {
const GENDERS = User.GENDERS;
const getTextNodeValues = node => {
if (node.nodeType === Node.TEXT_NODE) {
const nodeValue = node.nodeValue.replace(/"/,'');
return (/\S/.test(nodeValue)) ? [ nodeValue ] : [];
}
return Array.from(node.childNodes).flatMap(node => getTextNodeValues(node));
};
const fetchJsLinks = (html, user) => {
const jsLinks = Object.assign( {}, ...[ 'openProfile', 'sendMail', 'openChat', 'openMap' ].map(fncName => {
const linkElement = $(html).find(`a[href*="javascript:${fncName}("]`);
if (linkElement.length > 0) {
const href = linkElement.attr('href') || '';
const fncParam = ((href.match(/\((.+)\)/) || [])[1] || '').replace(/['"]/g,'');
const innerHtml = (linkElement.html() || '').trim();
return { [fncName]: { href, fncParam, html: innerHtml } };
}
}));
if (typeof jsLinks.sendMail !== 'undefined') {
user.uid = Number.parseInt(jsLinks.sendMail.fncParam);
}
if (typeof jsLinks.openProfile !== 'undefined') {
user.nick = jsLinks.openProfile.fncParam;
}
if (typeof jsLinks.openChat !== 'undefined') {
user.roomId = jsLinks.openChat.fncParam;
user.room = jsLinks.openChat.html;
}
if (typeof jsLinks.openMap !== 'undefined') {
const matchCoords = jsLinks.openMap.fncParam.match(/(-?\d{1,3}\.\d{8}),(-?\d{1,2}\.\d{8})/);
if (matchCoords !== null) {
user.longitudeDec = Number.parseFloat(matchCoords[1]);
user.latitudeDec = Number.parseFloat(matchCoords[2]);
}
user.location = jsLinks.openMap.html;
}
};
const fetchTextNodes = (html, user) => {
const textNodes = getTextNodeValues(html);
for (let gender of [ GENDERS.FEMALE, GENDERS.MALE, GENDERS.COUPLE ].map(enm => enm.text)) {
const match = textNodes[1].match(new RegExp(`^\\W*\(${gender}\),\\s\(\\d{1,3}\)`));
if (match !== null) {
user.age = Number.parseInt(match[2]);
user.gender = match[1];
break;
}
}
const matches = textNodes[2].match(/Online\s((\d{1,4})\s(minutes|hours|days)\s)?(ago|now|never)/);
if (matches !== null) {
const term = matches[4];
if (term === 'never') {
user.lastSeen = new Date(0);
user.isOnline = false;
} else {
const timeStampShort = new Date(user.timeStamp.getFullYear(), user.timeStamp.getMonth(), user.timeStamp.getDate(), user.timeStamp.getHours(), user.timeStamp.getMinutes());
if (term === 'ago') {
user.lastSeen = timeStampShort;
user.isOnline = false;
const value = Number.parseInt(matches[2]);
const unit = matches[3];
switch (unit) {
case 'minutes':
user.lastSeen.setMinutes(user.lastSeen.getMinutes() - value);
break;
case 'hours':
user.lastSeen.setMinutes(0);
user.lastSeen.setHours(user.lastSeen.getHours() - value);
break;
case 'days':
user.lastSeen.setMinutes(0);
user.lastSeen.setHours(0);
user.lastSeen.setDate(user.lastSeen.getDate() - value);
break;
}
} else if (term === 'now') {
user.lastSeen = timeStampShort;
user.isOnline = true;
}
}
}
for (let txt of textNodes.slice(2)) {
const distanceMatch = txt.match(/(\d{0,2},?\d{1,3})\skm\sfrom\syou/);
if (distanceMatch !== null) {
const distanceKm = Number.parseInt(distanceMatch[1].replace(/\D/g, ''));
if (Number.isInteger(distanceKm)) {
user.distanceKm = distanceKm;
}
}
}
};
const fetchImages = (html, user) => {
for (let [property, imageName] of Object.entries({ isPremium: 'premium.png', isSuper: 'super_premium.png', isReal: 'real.png' })) {
user[property] = $(html).find(`img[src$='/gfx/${imageName}']`).length > 0;
}
};
const fetchCustom = (html, user) => {
const embedRankSrc = $(html).find("embed[src^='/levelRank.swf?displayRank']").attr('src') || '';
const match = embedRankSrc.match(/displayRank=(\d{1,3})/);
if (match !== null) {
user.userLevel = Number.parseInt(match[1]);
}
};
return class UserControllerInternal {
constructor(user = new User()) {
this.user = user;
}
parseHtml({ html = '', url = '' } = {}) {
if (url.includes(CONST.URL_SEARCH)) {
fetchJsLinks(html, this.user);
fetchTextNodes(html, this.user);
fetchImages(html, this.user);
} else if (url.includes(CONST.URL_USER_LEVEL)) {
const matchUid = url.match(/uid=(\d{1,7})/);
if (matchUid !== null) {
this.user.uid = Number.parseInt(matchUid[1]);
}
const nick = $(html).find('div.popupcontent').find('div.bbottom').text();
if (nick) {
this.user.nick = nick;
}
fetchCustom(html, this.user);
}
return this;
}
static parseHtml({ html, url, user = new User() } = {}) {
return new UserControllerInternal(user).parseHtml({ html, url });
}
};
})();
const createUrlParamsFromUserSearch = userSearch => Object.fromEntries(
Object.entries({
nick: 'nick',
gender: 'gender',
page: 'page',
isOnline: 'online',
hasPicture: 'picture',
isPremium: 'isprem',
isReal: 'isreal',
isSuper: 'issuper'
})
.filter(([ propName, paramName ]) => userSearch[propName])
.map(([ propName, paramName ]) => {
let paramValue = userSearch[propName];
paramValue = (typeof paramValue === 'boolean') ? (paramValue ? 1 : 0) : String(paramValue).replace(/\s/g, '+');
return [ paramName, paramValue ];
})
);
const executeUserSearch = (userSearch) => {
return HttpRequestHtml.send({
url: CONST.URL_SEARCH, params: createUrlParamsFromUserSearch(userSearch)
}).then((response) => {
const userNodes = $(response.html).find('.searchNormal, .searchSuper').get();
const users = userNodes.map(node => UserControllerInternal.parseHtml({ html: node, url: response.finalUrl }).user);
return users;
});
};
return class UserSearch {
constructor({ nick = '', gender = User.GENDERS.ANY, isOnline = false, hasPicture = false, isPremium = false, isReal = false, isSuper = false, page = 0 } = {}) {
Object.assign(this, { nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper, page });
}
execute() {
return executeUserSearch(this).then(users => Promise.all(
users.map(user => HttpRequestHtml.send({
url: CONST.URL_USER_LEVEL, params: { uid: user.uid }
}).then(response => UserControllerInternal.parseHtml({ html: response.html, user: user, url: response.finalUrl }).user))
));
}
static execute(...args) {
return new UserSearch(...args).execute();
}
static byUid(uid) {
return HttpRequestHtml.send({
url: CONST.URL_USER_LEVEL, params: { uid: uid }
}).then(response => UserControllerInternal.parseHtml({ html: response.html, url: response.finalUrl }).user).then(user => {
if (!(user && user.nick)) {
return Promise.reject(new Error(`No result for User with uid '${uid}'.`));
}
return executeUserSearch(new UserSearch({ nick: user.nick }));
}).then(users => users.find(user => user.uid === uid ));
}
static get GENDERS() {
return User.GENDERS;
}
};
})();