Camamba Users Search Library

fetches Users

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/446634/1133718/Camamba%20Users%20Search%20Library.js

  1. // ==UserScript==
  2. // @name Camamba Users Search Library
  3. // @namespace hoehleg.userscripts.private
  4. // @version 0.0.9
  5. // @description fetches Users
  6. // @author Gerrit Höhle
  7. // @license MIT
  8. //
  9. // @require https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js?version=1063408
  10. //
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM.getValue
  13. // @grant GM.setValue
  14. // @grant GM.listValues
  15. // ==/UserScript==
  16.  
  17. // https://greasyfork.org/scripts/446634-camamba-users-search-library/
  18.  
  19. /* jslint esversion: 11 */
  20.  
  21. const cus_cache = (() => {
  22.  
  23. /**
  24. * @param {{ uid: number, name: string }} keyAsObject
  25. * @returns {string?}
  26. */
  27. const objectToKey = (keyAsObject) => {
  28. if (!keyAsObject?.uid && !keyAsObject?.name) {
  29. return null;
  30. }
  31. return `cus${keyAsObject.uid || ""}'${keyAsObject.name || ""}`;
  32. };
  33.  
  34. /**
  35. * @param {string} key
  36. * @returns {{ uid: number, name: string }}
  37. */
  38. const keyToObject = (keyAsString) => {
  39. const keyAsArray = keyAsString.slice(3).split("'");
  40. return {
  41. uid: Number.parseInt(keyAsArray[0]) || 0,
  42. name: keyAsArray[1] || "",
  43. };
  44. };
  45.  
  46. /** @type {object.<{ uid: number, name: string }>} */
  47. const cache = {};
  48. /** @type {object.<{ uid: number, name: string }>} */
  49. const keysByName = {};
  50. /** @type {object.<{ uid: number, name: string }>} */
  51. const keysByUid = {};
  52.  
  53. const addToCache = ({ uid, name, content }) => {
  54. const key = { uid, name };
  55.  
  56. if (uid) {
  57. const storedByUid = keysByUid[uid];
  58.  
  59. if (name) {
  60. if (storedByUid && storedByUid.name !== name) {
  61.  
  62. if (keysByName.hasOwnProperty(storedByUid.name)) {
  63. delete keysByName[storedByUid.name];
  64. }
  65.  
  66. GM.deleteKey(objectToKey(storedByUid));
  67. }
  68. keysByName[name] = key;
  69. }
  70.  
  71. keysByUid[uid] = key;
  72. }
  73. if (name) {
  74. keysByName[name] = key;
  75. }
  76. cache[objectToKey(key)] = content || null;
  77. };
  78.  
  79. (async () => {
  80. for (const keyAsString of (await GM.listValues()).filter(key => key.startsWith("cus"))) {
  81. addToCache(keyToObject(keyAsString));
  82. }
  83. })();
  84.  
  85. return {
  86.  
  87. /**
  88. * @param {{ uid: number?, name?: string, guessLog: GuessLog?, userLevel: UserLevel?}} param0
  89. */
  90. store: async ({ uid, name, guessLog, userLevel }) => {
  91. if (!name && !uid || !guessLog && !userLevel) {
  92. return;
  93. }
  94.  
  95. const content = {};
  96. if (userLevel?.level) {
  97. content.lvl = userLevel.level;
  98. }
  99. if (userLevel?.timeStamp) {
  100. content.lvlTS = userLevel.timeStamp;
  101. }
  102. if (guessLog?.ipList?.length) {
  103. content.ips = guessLog.ipList;
  104. }
  105. if (guessLog?.prints?.length) {
  106. content.prints = guessLog.prints;
  107. }
  108. if (guessLog?.scorePassword) {
  109. content.scrPW = guessLog.scorePassword;
  110. }
  111. if (guessLog?.scoreFinal) {
  112. content.scrFinal = guessLog.scoreFinal;
  113. }
  114. if (guessLog?.timeStamp) {
  115. content.scrTS = guessLog.timeStamp;
  116. }
  117.  
  118. const key = objectToKey({ name, uid });
  119. await GM.setValue(key, JSON.stringify(content));
  120. addToCache({ name, uid, content });
  121. },
  122.  
  123. /**
  124. * @param {{ uid: number, name: string }} param0
  125. * @return {Promise<{ guessLog : GuessLog, userLevel: UserLevel }}>
  126. */
  127. read: async ({ uid = 0, name = "" }) => {
  128. let keyAsObject = null;
  129. if (uid && keysByUid[uid]) {
  130. keyAsObject = keysByUid[uid];
  131. } else if (name && keysByName[name]) {
  132. keyAsObject = keysByName[name];
  133. }
  134. const key = objectToKey(keyAsObject);
  135. if (!key) {
  136. return {};
  137. }
  138.  
  139. let data = {};
  140. if (!cache[key]) {
  141. cache[key] = JSON.parse(await GM.getValue(key, "{}"));
  142. }
  143. data = cache[key];
  144.  
  145. const guessLog = new GuessLog({
  146. userName: keyAsObject?.name || name,
  147. ipList: data.ips || [],
  148. prints: data.prints || [],
  149. scorePassword: data.scrPW || 0,
  150. scoreFinal: data.scrFinal || 0,
  151. timeStamp: data.scrTS || 0,
  152. });
  153.  
  154. const userLevel = new UserLevel({
  155. name: keyAsObject?.name || name,
  156. uid: keyAsObject?.uid || uid,
  157. level: data.lvl || 0,
  158. timeStamp: data.lvlTS || 0,
  159. });
  160.  
  161. return { guessLog, userLevel };
  162. },
  163. };
  164. })();
  165.  
  166. class GuessLog {
  167. /** @param {{ userName: string, ipList: string[], prints: string[], scorePassword: number, scoreFinal: number, timeStamp: number }} param0 */
  168. constructor({ userName, ipList, prints, scorePassword, scoreFinal, timeStamp }) {
  169. /** @type {string} */
  170. this.userName = userName;
  171. /** @type {string[]} */
  172. this.ipList = ipList;
  173. /** @type {string[]} */
  174. this.prints = prints;
  175. /** @type {number} */
  176. this.scorePassword = scorePassword;
  177. /** @type {number} */
  178. this.scoreFinal = scoreFinal;
  179. /** @type {number} */
  180. this.timeStamp = timeStamp;
  181. }
  182. }
  183.  
  184. /**
  185. * @typedef {object} UserParams
  186. * @property {string} name
  187. * @property {uid} [number]
  188. * @property {'male'|'female'|'couple'?} [gender]
  189. * @property {number} [age]
  190. * @property {number} [level]
  191. * @property {number} [longitude]
  192. * @property {number} [latitude]
  193. * @property {string} [location]
  194. * @property {number} [distanceKM]
  195. * @property {boolean} [isReal]
  196. * @property {boolean} [hasPremium]
  197. * @property {boolean} [hasSuper]
  198. * @property {boolean} [isPerma]
  199. * @property {boolean} [isOnline]
  200. * @property {string} [room]
  201. * @property {Date} [lastSeen]
  202. * @property {Date} [regDate]
  203. * @property {string[]} [ipList]
  204. * @property {Date} [scorePassword]
  205. * @property {Date} [scoreFinal]
  206. * @property {(date: Date) => string} [dateToHumanReadable]
  207. */
  208.  
  209. class GuessLogSearch extends HttpRequestHtml {
  210.  
  211. constructor(name) {
  212. /**
  213. * @param {string} labelText
  214. * @param {string} textContent
  215. * @returns {number}
  216. */
  217. const matchScore = (labelText, textContent) => {
  218. const regexLookBehind = new RegExp("(?<=" + labelText + ":\\s)");
  219. const regexFloat = /\d{1,2}\.?\d{0,20}/;
  220. const regexLookAhead = /(?=\spoints)/;
  221.  
  222. for (const regexesToJoin of [
  223. [regexLookBehind, regexFloat, regexLookAhead],
  224. [regexLookBehind, regexFloat]
  225. ]) {
  226. const regexAsString = regexesToJoin.map(re => re.source).join("");
  227. const matcher = new RegExp(regexAsString, "i").exec(textContent);
  228. if (matcher != null) {
  229. return Number.parseFloat(matcher[0]);
  230. }
  231. }
  232. };
  233.  
  234. /**
  235. * @param {RegExp} regex
  236. * @param {string} textContent
  237. * @returns {Array<String>}
  238. */
  239. const matchList = (regex, textContent) => {
  240. const results = [...textContent.matchAll(regex)].reduce((a, b) => [...a, ...b], []);
  241. if (results.length) {
  242. const resultsDistinct = [...new Set(results)];
  243. return resultsDistinct;
  244. }
  245. };
  246.  
  247. super({
  248. url: 'https://www.camamba.com/guesslog.php',
  249. params: { name },
  250.  
  251. /**
  252. * @param {{ html: Document}} resp
  253. * @returns {GuessLog}
  254. */
  255. resultTransformer: (resp) => {
  256. const textContent = resp.html.body.textContent;
  257.  
  258. 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);
  259. const prints = matchList(/(?<=Print\d{0,2}\schecked\sis\s)[0-9a-f]+/g, textContent);
  260.  
  261. const scorePassword = matchScore("password check", textContent);
  262. const scoreFinal = matchScore("final score", textContent);
  263.  
  264. return { userName: name, ipList, prints, scorePassword, scoreFinal, timeStamp: new Date().getTime() };
  265. }
  266. });
  267. }
  268.  
  269. /** @returns {Promise<GuessLog>} */
  270. async send() {
  271. const maxHoursInCache = 6;
  272.  
  273. const name = this.params.name;
  274. const cache = await cus_cache.read({ name });
  275.  
  276. let result = cache?.guessLog;
  277. const timeStamp = result?.timeStamp;
  278.  
  279. if (!timeStamp || new Date().getTime() - timeStamp >= maxHoursInCache * 60 * 60 * 1000) {
  280. result = await super.send();
  281. cus_cache.store({ ...(cache || {}), name, guessLog: result });
  282. }
  283.  
  284. return result;
  285. }
  286.  
  287. /**
  288. * @param {string} name
  289. * @returns {Promise<GuessLog>}
  290. */
  291. static async send(name) {
  292. return await new GuessLogSearch(name).send();
  293. }
  294. }
  295.  
  296. /**
  297. * @typedef {Object} BanLog
  298. * @property {string} moderator - user or moderator who triggered the log
  299. * @property {string} user - user who is subject
  300. * @property {Date} date - date of this log
  301. * @property {string} reason - content
  302. */
  303.  
  304. class BannLogSearch extends HttpRequestHtml {
  305. /**
  306. * @param {number} uid
  307. */
  308. constructor(uid = null) {
  309. super({
  310. url: 'https://www.camamba.com/banlog.php',
  311. params: uid ? { admin: uid } : {},
  312. resultTransformer: (response, _request) => {
  313. const results = [];
  314. const xPathExpr = "//tr" + ['User', 'Moderator', 'Date', 'Reason'].map(hdrText => `[td[span[text()='${hdrText}']]]`).join("");
  315. let tr = (response.html.evaluate(xPathExpr, response.html.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue || {}).nextElementSibling;
  316.  
  317. while (tr) {
  318. const tds = tr.querySelectorAll('td');
  319. const user = tds[0].querySelector("a") || tds[0].textContent;
  320. const moderator = tds[1].textContent;
  321.  
  322. let date;
  323. const dateMatch = /(\d{2}).(\d{2}).(\d{4})<br>(\d{1,2}):(\d{2}):(\d{2})/.exec(tds[2].innerHTML);
  324. if (dateMatch) {
  325. const day = dateMatch[1];
  326. const month = dateMatch[2];
  327. const year = dateMatch[3];
  328. const hour = dateMatch[4];
  329. const minute = dateMatch[5];
  330. const second = dateMatch[6];
  331. date = new Date(year, month - 1, day, hour, minute, second);
  332. }
  333.  
  334. const reason = tds[3].textContent;
  335. results.push({ user, moderator, date, reason });
  336.  
  337. tr = tr.nextElementSibling;
  338. }
  339.  
  340. return results;
  341. }
  342. });
  343. }
  344.  
  345. /**
  346. * @param {number} uid
  347. * @returns {Promise<BanLog[]>}
  348. */
  349. static async send(uid) {
  350. return await new BannLogSearch(uid).send();
  351. }
  352. }
  353.  
  354. class GalleryImage {
  355. constructor({ dataURI, href }) {
  356. /** @type {string} */
  357. this.dataURI = dataURI;
  358. /** @type {string} */
  359. this.href = href;
  360. }
  361. }
  362.  
  363.  
  364. class UserLevel {
  365. /** @param {{ level: number, uid: number, name: string, timeStamp: number }} param0 */
  366. constructor({ level, uid = null, name = null, timeStamp = null }) {
  367. /** @type {number} */
  368. this.level = level !== null ? Number.parseInt(level) : null;
  369. /** @type {number} */
  370. this.uid = uid !== null ? Number.parseInt(uid) : null;
  371. /** @type {string} */
  372. this.name = name;
  373. /** @type {number} */
  374. this.timeStamp = timeStamp !== null ? Number.parseInt(timeStamp) : null;
  375. }
  376. }
  377.  
  378. class UserLevelSearch extends HttpRequestHtml {
  379. constructor(uid) {
  380. super({
  381. url: 'https://www.camamba.com/user_level.php',
  382. params: { uid },
  383.  
  384. /**
  385. * @param {{ html: Document }} response
  386. * @param {{ params: { uid: number }}} request
  387. * @returns {UserLevel}
  388. */
  389. resultTransformer: (response, request) => {
  390. const html = response.html;
  391.  
  392. let name = null, level = null;
  393.  
  394. const nameElement = html.querySelector('b');
  395. if (nameElement) {
  396. name = nameElement.textContent;
  397. }
  398.  
  399. const levelElement = html.querySelector('font.xxltext');
  400. if (levelElement) {
  401. const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
  402. if (levelMatch) {
  403. level = Number.parseInt(levelMatch);
  404. }
  405. }
  406.  
  407. return new UserLevel({ uid: request.params.uid, name, level, timeStamp: new Date().getTime() });
  408. }
  409. });
  410. }
  411.  
  412. /**
  413. * @returns {Promise<UserLevel>}
  414. */
  415. async send() {
  416. const maxHoursInCache = 24;
  417.  
  418. const uid = this.params.uid;
  419. const cache = await cus_cache.read({ uid });
  420.  
  421. let result = cache?.userLevel;
  422. const timeStamp = result?.timeStamp;
  423.  
  424. if (!timeStamp || new Date().getTime() - timeStamp >= maxHoursInCache * 60 * 60 * 1000) {
  425. result = await super.send();
  426. cus_cache.store({ ...(cache || {}), uid, userLevel: result });
  427. }
  428.  
  429. return result;
  430. }
  431.  
  432. /**
  433. * @param {number} uid
  434. * @returns {Promise<UserLevel>}
  435. */
  436. static async send(uid) {
  437. return await new UserLevelSearch(uid).send();
  438. }
  439. }
  440.  
  441. class User {
  442. /** @param {UserParams} param0 */
  443. constructor({
  444. name, uid = 0, gender = null, age = null,
  445. longitude = null, latitude = null, location = null, distanceKM = null,
  446. isReal = null, hasPremium = null, hasSuper = null, isPerma = null,
  447. isOnline = null, room = null, lastSeen = null, regDate = null,
  448. dateToHumanReadable = (date) => date ?
  449. date.toLocaleString('de-DE', { timeStyle: "medium", dateStyle: "short", timeZone: 'CET' }) : '',
  450. }) {
  451. /** @type {string} */
  452. this.name = String(name);
  453. /** @type {number?} */
  454. this.uid = uid;
  455. /** @type {'male'|'female'|'couple'?} */
  456. this.gender = gender;
  457. /** @type {number?} */
  458. this.age = age;
  459.  
  460. /** @type {number?} */
  461. this.longitude = longitude;
  462. /** @type {number?} */
  463. this.latitude = latitude;
  464. /** @type {string?} */
  465. this.location = location;
  466. /** @type {number?} */
  467. this.distanceKM = distanceKM;
  468.  
  469. /** @type {boolean?} */
  470. this.isReal = isReal;
  471. /** @type {boolean?} */
  472. this.hasPremium = hasPremium;
  473. /** @type {boolean?} */
  474. this.hasSuper = hasSuper;
  475. /** @type {boolean?} */
  476. this.isPerma = isPerma;
  477.  
  478. /** @type {boolean?} */
  479. this.isOnline = isOnline;
  480. /** @type {string?} */
  481. this.room = room;
  482. /** @type {Date?} */
  483. this.lastSeen = lastSeen;
  484. /** @type {Date?} */
  485. this.regDate = regDate;
  486.  
  487. /** @type {string[]} */
  488. this.prints = [];
  489. /** @type {string[]} */
  490. this.ipList = [];
  491. /** @type {number?} */
  492. this.scorePassword = null;
  493. /** @type {number?} */
  494. this.scoreFinal = null;
  495. /** @type {number} */
  496. this.guessLogTS = null;
  497.  
  498. /** @type {(date: Date) => string} */
  499. this.dateToHumanReadable = dateToHumanReadable;
  500.  
  501. /** @type {number?} */
  502. this.level = null;
  503. /** @type {number} */
  504. this.levelTS = null;
  505.  
  506. /** @type {string[]} */
  507. this.galleryData = [];
  508. /** @type {number} */
  509. this.galleryDataTS = null;
  510. }
  511.  
  512. /** @type {string} @readonly */
  513. get lastSeenHumanReadable() {
  514. return this.dateToHumanReadable(this.lastSeen);
  515. }
  516.  
  517. /** @type {string} @readonly */
  518. get regDateHumanReadable() {
  519. return this.dateToHumanReadable(this.regDate);
  520. }
  521.  
  522. get galleryAsImgElements() {
  523. if (!this.galleryData) {
  524. return [];
  525. }
  526.  
  527. return this.galleryData.map(data => Object.assign(document.createElement('img'), {
  528. src: data.dataURI
  529. }));
  530. }
  531.  
  532. async updateGalleryHref() {
  533. const pictureLinks = (await HttpRequestHtml.send({
  534. url: "https://www.camamba.com/profile_view.php",
  535. params: Object.assign(
  536. { m: 'gallery' },
  537. this.uid ? { uid: this.uid } : { user: this.name }
  538. ),
  539.  
  540. pageNr: 0,
  541. pagesMaxCount: 500,
  542.  
  543. resultTransformer: (response) => {
  544. const hrefList = [...response.html.querySelectorAll("img.picborder")].map(img => img.src);
  545. return hrefList.map(href => href.slice(0, 0 - ".s.jpg".length) + ".l.jpg");
  546. },
  547. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  548. return lastResult.length >= 15;
  549. },
  550. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  551. })).flat();
  552.  
  553. this.galleryData = pictureLinks.map(href => ({ href }));
  554. this.galleryDataTS = new Date().getTime();
  555. }
  556.  
  557. async updateGalleryData(includeUpdateOfHref = true) {
  558. if (includeUpdateOfHref) {
  559. await this.updateGalleryHref();
  560. }
  561.  
  562. const readGalleryData = this.galleryData.map(({ href }) => (async () => {
  563. const dataURI = await HttpRequestBlob.send({ url: href });
  564. return new GalleryImage({ dataURI, href });
  565. })());
  566.  
  567. this.galleryData = await Promise.all(readGalleryData);
  568. this.galleryDataTS = new Date().getTime();
  569. }
  570.  
  571. async updateLevel() {
  572. const { level, timeStamp, name } = await UserLevelSearch.send(this.uid);
  573. this.level = level;
  574. this.levelTS = timeStamp;
  575. this.name = name;
  576. }
  577.  
  578. async updateGuessLog() {
  579. /** @type {GuessLog} */
  580. const guessLog = await GuessLogSearch.send(this.name);
  581. this.guessLogTS = new Date().getTime();
  582.  
  583. this.ipList = guessLog.ipList;
  584. this.prints = guessLog.prints;
  585. this.scorePassword = guessLog.scorePassword;
  586. this.scoreFinal = guessLog.scoreFinal;
  587. }
  588.  
  589. async addNote(text) {
  590. if (!this.uid) {
  591. return await Promise.reject({
  592. status: 500,
  593. statusText: "missing uid"
  594. });
  595. }
  596.  
  597. return await new Promise((res, rej) => GM_xmlhttpRequest({
  598. url: 'https://www.camamba.com/profile_view.php',
  599. method: 'POST',
  600. data: `uid=${this.uid}&modnote=${encodeURIComponent(text)}&m=admin&nomen=1`,
  601. headers: {
  602. "Content-Type": "application/x-www-form-urlencoded"
  603. },
  604. onload: (xhr) => {
  605. res(xhr.responseText);
  606. },
  607. onerror: (xhr) => rej({
  608. status: xhr.status,
  609. statusText: xhr.statusText
  610. }),
  611. }));
  612. }
  613. }
  614.  
  615. class UserSearch extends HttpRequestHtml {
  616. /** @param {{
  617. * name: string?,
  618. * uid: number?,
  619. * gender: ('any' | 'male' | 'female' |'couple')?,
  620. * isOnline: boolean?, hasReal: boolean?, hasPremium: boolean?, hasSuper: boolean?, hasPicture: boolean?,
  621. * isSortByRegDate: boolean?,
  622. * isSortByDistance: boolean?,
  623. * isShowAll: boolean?,
  624. * pageNr: number?,
  625. * pagesMaxCount: number?,
  626. * keepInCacheTimoutMs: number?
  627. * }} param0 */
  628. constructor({
  629. name = null,
  630. uid = 0,
  631. gender = 'any',
  632. isOnline = null,
  633. hasReal = null,
  634. hasPremium = null,
  635. hasSuper = null,
  636. hasPicture = null,
  637. isSortByRegDate = null,
  638. isSortByDistance = null,
  639. isShowAll = null,
  640. pageNr = 1,
  641. pagesMaxCount = 1,
  642. keepInCacheTimoutMs
  643. } = {}) {
  644. let params = Object.assign(
  645. (name ? {
  646. nick: name
  647. } : {}),
  648. {
  649. gender: gender.toLowerCase(),
  650. },
  651. Object.fromEntries(Object.entries({
  652. online: isOnline,
  653. isreal: hasReal,
  654. isprem: hasPremium,
  655. issuper: hasSuper,
  656. picture: hasPicture,
  657. sortreg: isSortByRegDate,
  658. byDistance: isSortByDistance,
  659. showall: isShowAll,
  660. })
  661. .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
  662. .map(([k, v]) => ([[k], v ? 1 : 0])))
  663. );
  664.  
  665. params = Object.entries(params).map(([key, value]) => key + '=' + value).join('&');
  666.  
  667. if (params.length) {
  668. params += "&";
  669. }
  670. params += `page=${Math.max(pageNr - 1, 0)}`;
  671.  
  672. super({
  673. url: 'https://www.camamba.com/search.php',
  674. params,
  675. pageNr: Math.max(pageNr, 1),
  676. pagesMaxCount: Math.max(pagesMaxCount, 1),
  677. keepInCacheTimoutMs,
  678.  
  679. resultTransformer: (response) => {
  680. /** @type {Array<User>} */
  681. const users = [];
  682.  
  683. for (const trNode of response.html.querySelectorAll('.searchSuper tr, .searchNormal tr')) {
  684. const innerHTML = trNode.innerHTML;
  685.  
  686. const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);
  687.  
  688. if (!nameMatch) {
  689. break;
  690. }
  691.  
  692. const user = new User({
  693. name: nameMatch[1],
  694. isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
  695. hasPremium: /<a href="\/premium.php">/.test(innerHTML),
  696. hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
  697. isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
  698. });
  699.  
  700. const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML) || /<img\ssrc="\/userpics\/(\d{1,8})/.exec(innerHTML);
  701. if (uidMatch) {
  702. user.uid = Number.parseInt(uidMatch[1]);
  703. }
  704.  
  705. // Längengrad, Breitengrad, Ortsname
  706. const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
  707. if (locationMatch) {
  708. user.longitude = Number.parseFloat(locationMatch[1]);
  709. user.latitude = Number.parseFloat(locationMatch[2]);
  710. user.location = locationMatch[3];
  711. }
  712.  
  713. // Entfernung in km
  714. const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
  715. if (distanceMatch) {
  716. user.distanceKM = parseInt(distanceMatch[1]);
  717. }
  718.  
  719. // Geschlecht und Alter
  720. const genderAgeMatch = /(male|female|couple),\s(\d{1,4})(?:<br>){2}Online/.exec(innerHTML);
  721. if (genderAgeMatch) {
  722. user.gender = genderAgeMatch[1];
  723. user.age = genderAgeMatch[2];
  724. }
  725.  
  726. // zuletzt Online
  727. if (user.isOnline) {
  728. user.lastSeen = new Date();
  729. } else {
  730. const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
  731. if (lastSeenMatch) {
  732. const value = parseInt(lastSeenMatch[1]);
  733.  
  734. const factorToMillis = {
  735. 'minutes': 1000 * 60,
  736. 'hours': 1000 * 60 * 60,
  737. 'days': 1000 * 60 * 60 * 24,
  738. }[lastSeenMatch[2]];
  739.  
  740. user.lastSeen = new Date(Date.now() - value * factorToMillis);
  741. }
  742. }
  743.  
  744. // Raumname
  745. const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
  746. if (roomMatch) {
  747. user.room = roomMatch[1];
  748. }
  749.  
  750. // regDate
  751. const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
  752. if (regDateMatch) {
  753. const regDateDay = regDateMatch[1];
  754. const regDateMonth = regDateMatch[2];
  755. const regDateYear = regDateMatch[3];
  756. const regDateHour = regDateMatch[4];
  757. const regDateMinute = regDateMatch[5];
  758. const regDateSecond = regDateMatch[6];
  759. user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
  760. }
  761.  
  762. users.push(user);
  763. }
  764.  
  765. return users;
  766. },
  767.  
  768. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  769. return lastResult.length >= 50;
  770. },
  771.  
  772. paramsConfiguratorForPageNr: (params, pageNr) => {
  773. return params.replace(/page=\d+(?:$)/, `page=${pageNr - 1}`);
  774. },
  775. });
  776. this.uid = uid || null;
  777. }
  778.  
  779. /** @returns {Promise<User[]>} */
  780. async send() {
  781. if (this.uid) {
  782. const user = new User({ uid: this.uid });
  783. await user.updateLevel();
  784.  
  785. if (!user.name || user.level) {
  786. return [];
  787. }
  788. if (this.params.nick) {
  789. const unameURIencoded = encodeURIComponent(user.name.toLowerCase());
  790. const unameFromSearchParam = encodeURIComponent(this.params.nick.toLowerCase()).trim();
  791. if (unameURIencoded.includes(unameFromSearchParam)) {
  792. return [];
  793. }
  794. }
  795.  
  796. this.params.nick = user.name;
  797. const result = (await super.send()).flat().find(u => u.uid == this.uid);
  798. if (!result) {
  799. return [];
  800. }
  801.  
  802. return [Object.assign(user, result)];
  803. }
  804.  
  805. return (await super.send()).flat();
  806. }
  807.  
  808. /**
  809. * @param {{
  810. * name: string,
  811. * uid: number?,
  812. * gender: 'any' | 'male' | 'female' |'couple',
  813. * isOnline: boolean,hasReal: boolean, hasPremium: boolean, hasSuper: boolean, hasPicture: boolean,
  814. * isSortByRegDate: boolean,
  815. * isSortByDistance: boolean,
  816. * isShowAll: boolean,
  817. * pageNr: number,
  818. * pagesMaxCount: number,
  819. * keepInCacheTimoutMs: number
  820. * }} param0
  821. * @returns {Promise<User[]>}
  822. */
  823. static async send(param0) {
  824. return await new UserSearch(param0).send();
  825. }
  826. }