Camamba Users Search Library

fetches Users

当前为 2023-01-01 提交的版本,查看 最新版本

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