- // ==UserScript==
- // @name Camamba Users Search Library
- // @namespace hoehleg.userscripts.private
- // @version 0.0.1
- // @description fetches Users
- // @author Gerrit Höhle
- // @license MIT
- //
- // @require https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js?version=1062443
- //
- // @grant GM_xmlhttpRequest
- // ==/UserScript==
-
- /* jslint esversion: 9 */
- class GuessLogSearch extends HttpRequestHtml {
-
- constructor(name) {
- /**
- * @param {string} labelText
- * @param {string} textContent
- * @returns {number}
- */
- const matchScore = (labelText, textContent) => {
- const regexLookBehind = new RegExp("(?<=" + labelText + ":\\s)");
- const regexFloat = /\d{1,2}\.?\d{0,20}/;
- const regexLookAhead = /(?=\spoints)/;
-
- for (const regexesToJoin of [
- [regexLookBehind, regexFloat, regexLookAhead],
- [regexLookBehind, regexFloat]
- ]) {
- const regexAsString = regexesToJoin.map(re => re.source).join("");
- const matcher = new RegExp(regexAsString, "i").exec(textContent);
- if (matcher != null) {
- return Number.parseFloat(matcher[0]);
- }
- }
- };
-
- /**
- * @param {RegExp} regex
- * @param {string} textContent
- * @returns {Array<String>}
- */
- const matchList = (regex, textContent) => {
- const results = [...textContent.matchAll(regex)].reduce((a, b) => [...a, ...b], []);
- if (results.length) {
- const resultsDistinct = [...new Set(results)];
- return resultsDistinct;
- }
- };
-
- super({
- url: 'https://www.camamba.com/guesslog.php',
- params: { name },
- resultTransformer: (resp) => {
- const textContent = resp.html.body.textContent;
-
- const ipList = matchList(/(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])/g, textContent);
- const prints = matchList(/(?<=Print\d{0,2}\schecked\sis\s)[0-9a-f]+/g, textContent);
-
- const scorePassword = matchScore("password check", textContent);
- const scoreFinal = matchScore("final score", textContent);
-
- return { userName: name, ipList, prints, scorePassword, scoreFinal };
- }
- });
- }
-
- /** @returns {Promise<GuessLog>} */
- async send() {
- return await super.send();
- }
-
- /**
- * @param {string} name
- * @returns {Promise<GuessLog>}
- */
- static async send(name) {
- return await new GuessLogSearch(name).send();
- }
- }
-
- class User {
- /** @param {UserParams} param0 */
- constructor({
- name, uid = 0, gender = null, age = null,
- longitude = null, latitude = null, location = null, distanceKM = null,
- isReal = null, hasPremium = null, hasSuper = null, isPerma = null,
- isOnline = null, room = null, lastSeen = null, regDate = null,
- dateToHumanReadable = (date) => date ?
- date.toLocaleString('de-DE', { timeStyle: "medium", dateStyle: "short", timeZone: 'CET' }) : '',
- }) {
- /** @type {string} */
- this.name = String(name);
- /** @type {number?} */
- this.uid = uid;
- /** @type {'male'|'female'|'couple'?} */
- this.gender = gender;
- /** @type {number?} */
- this.age = age;
-
- /** @type {number?} */
- this.longitude = longitude;
- /** @type {number?} */
- this.latitude = latitude;
- /** @type {string?} */
- this.location = location;
- /** @type {number?} */
- this.distanceKM = distanceKM;
-
- /** @type {boolean?} */
- this.isReal = isReal;
- /** @type {boolean?} */
- this.hasPremium = hasPremium;
- /** @type {boolean?} */
- this.hasSuper = hasSuper;
- /** @type {boolean?} */
- this.isPerma = isPerma;
-
- /** @type {boolean?} */
- this.isOnline = isOnline;
- /** @type {string?} */
- this.room = room;
- /** @type {Date?} */
- this.lastSeen = lastSeen;
- /** @type {Date?} */
- this.regDate = regDate;
-
- /** @type {string[]} */
- this.prints = [];
- /** @type {string[]} */
- this.ipList = [];
- /** @type {number?} */
- this.scorePassword = null;
- /** @type {number?} */
- this.scoreFinal = null;
- /** @type {number} */
- this.guessLogTS = null;
-
- /** @type {(date: Date) => string} */
- this.dateToHumanReadable = dateToHumanReadable;
-
- /** @type {number?} */
- this.level = null;
- /** @type {number} */
- this.levelTS = null;
-
- /** @type {string[]} */
- this.galleryData = [];
- /** @type {number} */
- this.galleryDataTS = null;
- }
-
- /** @type {string} @readonly */
- get lastSeenHumanReadable() {
- return this.dateToHumanReadable(this.lastSeen);
- }
-
- /** @type {string} @readonly */
- get regDateHumanReadable() {
- return this.dateToHumanReadable(this.regDate);
- }
-
- get galleryAsImgElements() {
- if (!this.galleryData) {
- return [];
- }
-
- return this.galleryData.map(data => Object.assign(document.createElement('img'), {
- src: data
- }));
- }
-
- async updateGalleryData() {
- const pictureLinks = (await HttpRequestHtml.send({
- url: "https://www.camamba.com/profile_view.php",
- params: Object.assign(
- { m: 'gallery' },
- this.uid ? { uid: this.uid } : { user: this.name }
- ),
-
- pageNr: 1,
- pagesMaxCount: 500,
-
- resultTransformer: (response) => {
- const hrefList = [...response.html.querySelectorAll("img.picborder")].map(img => img.src);
- return hrefList.map(href => href.slice(0, 0 - ".s.jpg".length) + ".l.jpg");
- },
- hasNextPage: (_resp, _httpRequestHtml, lastResult) => lastResult.length >= 15,
- paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr - 1 }),
- })).flat();
-
- this.galleryData = await Promise.all(pictureLinks.map(url => HttpRequestBlob.send({ url })));
- this.galleryDataTS = new Date().getTime();
- }
-
- async updateLevel() {
- this.level = await HttpRequestHtml.send({
- url: 'https://www.camamba.com/user_level.php',
- params: { uid: this.uid },
- resultTransformer: (response) => {
- const levelElement = response.html.querySelector('font.xxltext');
- if (levelElement) {
- const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
- if (levelMatch) {
- return parseInt(levelMatch);
- }
- }
- return null;
- }
- });
- this.levelTS = new Date().getTime();
- }
-
- async updateGuessLog() {
- /** @type {GuessLog} */
- const guessLog = await GuessLogSearch.send(this.name);
- this.guessLogTS = new Date().getTime();
-
- this.ipList = guessLog.ipList;
- this.prints = guessLog.prints;
- this.scorePassword = guessLog.scorePassword;
- this.scoreFinal = guessLog.scoreFinal;
- }
-
- stringify() {
-
- }
-
- static parse() {
- }
- }
-
- class UserSearch extends HttpRequestHtml {
- /** @param {UserSearchParams} param0 */
- constructor({
- name = '',
- gender = 'any',
- isOnline = null,
- hasReal = null,
- hasPremium = null,
- hasSuper = null,
- hasPicture = null,
- isSortByRegDate = null,
- isSortByDistance = null,
- isShowAll = null,
- pageNr = 1,
- pagesMaxCount = 1,
- keepInCacheTimoutMs
- } = {}) {
- super({
- url: 'https://www.camamba.com/search.php',
- params: Object.assign(
- {
- nick: name,
- gender: gender.toLowerCase(),
- page: Math.max(pageNr - 1, 0),
- },
- Object.fromEntries(Object.entries({
- online: isOnline,
- isreal: hasReal,
- isprem: hasPremium,
- issuper: hasSuper,
- picture: hasPicture,
- sortreg: isSortByRegDate,
- byDistance: isSortByDistance,
- showall: isShowAll,
- })
- .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
- .map(([k, v]) => ([[k], v ? 1 : 0])))
- ),
- pageNr: Math.max(pageNr, 1),
- pagesMaxCount: Math.max(pagesMaxCount, 1),
- keepInCacheTimoutMs,
-
- resultTransformer: (response) => {
- /** @type {Array<User>} */
- const users = [];
-
- for (const tdNode of response.html.querySelectorAll('.searchSuper td:nth-child(2), .searchNormal td:nth-child(2)')) {
- const innerHTML = tdNode.innerHTML;
- const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML);
- const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);
- if (!uidMatch || !nameMatch) {
- break;
- }
-
- const user = new User({
- name: nameMatch[1],
- uid: Number.parseInt(uidMatch[1]),
- isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
- hasPremium: /<a href="\/premium.php">/.test(innerHTML),
- hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
- isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
- });
-
- // Längengrad, Breitengrad, Ortsname
- const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
- if (locationMatch) {
- user.longitude = Number.parseFloat(locationMatch[1]);
- user.latitude = Number.parseFloat(locationMatch[2]);
- user.location = locationMatch[3];
- }
-
- // Entfernung in km
- const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
- if (distanceMatch) {
- user.distanceKM = parseInt(distanceMatch[1]);
- }
-
- // Geschlecht und Alter
- const genderAgeMatch = /(male|female|couple),\s(\d{1,3})(?:<br>){2}Online/.exec(innerHTML);
- if (genderAgeMatch) {
- user.gender = genderAgeMatch[1];
- user.age = genderAgeMatch[2];
- }
-
- // zuletzt Online
- if (user.isOnline) {
- user.lastSeen = new Date();
- } else {
- const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
- if (lastSeenMatch) {
- const value = parseInt(lastSeenMatch[1]);
-
- const factorToMillis = {
- 'minutes': 1000 * 60,
- 'hours': 1000 * 60 * 60,
- 'days': 1000 * 60 * 60 * 24,
- }[lastSeenMatch[2]];
-
- user.lastSeen = new Date(Date.now() - value * factorToMillis);
- }
- }
-
- // Raumname
- const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
- if (roomMatch) {
- user.room = roomMatch[1];
- }
-
- // regDate
- const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
- if (regDateMatch) {
- const regDateDay = regDateMatch[1];
- const regDateMonth = regDateMatch[2];
- const regDateYear = regDateMatch[3];
- const regDateHour = regDateMatch[4];
- const regDateMinute = regDateMatch[5];
- const regDateSecond = regDateMatch[6];
- user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
- }
-
- users.push(user);
- }
-
- return users;
- },
-
- hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
- return lastResult.length >= 50;
- },
-
- paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
- });
- }
-
- /** @returns {Promise<User[]>} */
- async send() {
- return (await super.send()).flat();
- }
-
- /**
- * @param {UserSearchParams} param0
- * @returns {Promise<User[]>}
- */
- static async send(param0) {
- return await new UserSearch(param0).send();
- }
- }