Camamba Users Search Library

fetches Users

当前为 2022-06-26 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/446634/1064782/Camamba%20Users%20Search%20Library.js

  1. // ==UserScript==
  2. // @name Camamba Users Search Library
  3. // @namespace hoehleg.userscripts.private
  4. // @version 0.0.2
  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/de/scripts/446634-camamba-users-search-library
  15.  
  16. /* jslint esversion: 9 */
  17. class GuessLogSearch extends HttpRequestHtml {
  18.  
  19. constructor(name) {
  20. /**
  21. * @param {string} labelText
  22. * @param {string} textContent
  23. * @returns {number}
  24. */
  25. const matchScore = (labelText, textContent) => {
  26. const regexLookBehind = new RegExp("(?<=" + labelText + ":\\s)");
  27. const regexFloat = /\d{1,2}\.?\d{0,20}/;
  28. const regexLookAhead = /(?=\spoints)/;
  29.  
  30. for (const regexesToJoin of [
  31. [regexLookBehind, regexFloat, regexLookAhead],
  32. [regexLookBehind, regexFloat]
  33. ]) {
  34. const regexAsString = regexesToJoin.map(re => re.source).join("");
  35. const matcher = new RegExp(regexAsString, "i").exec(textContent);
  36. if (matcher != null) {
  37. return Number.parseFloat(matcher[0]);
  38. }
  39. }
  40. };
  41.  
  42. /**
  43. * @param {RegExp} regex
  44. * @param {string} textContent
  45. * @returns {Array<String>}
  46. */
  47. const matchList = (regex, textContent) => {
  48. const results = [...textContent.matchAll(regex)].reduce((a, b) => [...a, ...b], []);
  49. if (results.length) {
  50. const resultsDistinct = [...new Set(results)];
  51. return resultsDistinct;
  52. }
  53. };
  54.  
  55. super({
  56. url: 'https://www.camamba.com/guesslog.php',
  57. params: { name },
  58. resultTransformer: (resp) => {
  59. const textContent = resp.html.body.textContent;
  60.  
  61. 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);
  62. const prints = matchList(/(?<=Print\d{0,2}\schecked\sis\s)[0-9a-f]+/g, textContent);
  63.  
  64. const scorePassword = matchScore("password check", textContent);
  65. const scoreFinal = matchScore("final score", textContent);
  66.  
  67. return { userName: name, ipList, prints, scorePassword, scoreFinal };
  68. }
  69. });
  70. }
  71.  
  72. /** @returns {Promise<GuessLog>} */
  73. async send() {
  74. return await super.send();
  75. }
  76.  
  77. /**
  78. * @param {string} name
  79. * @returns {Promise<GuessLog>}
  80. */
  81. static async send(name) {
  82. return await new GuessLogSearch(name).send();
  83. }
  84. }
  85.  
  86. class GalleryImage {
  87. constructor({ dataURI, href }) {
  88. /** @type {string} */
  89. this.dataURI = dataURI;
  90. /** @type {string} */
  91. this.href = href;
  92. }
  93. }
  94.  
  95.  
  96. class UserLevel {
  97. constructor({ level, uid = null, name = null, timeStamp = null }) {
  98.  
  99. /** @type {number} */
  100. this.level = level !== null ? Number.parseInt(level) : null;
  101.  
  102. /** @type {number} */
  103. this.uid = uid !== null ? Number.parseInt(uid) : null;
  104.  
  105. /** @type {string} */
  106. this.name = name;
  107.  
  108. /** @type {number} */
  109. this.timeStamp = timeStamp !== null ? Number.parseInt(timeStamp) : null;
  110. }
  111. }
  112.  
  113. class UserLevelSearch extends HttpRequestHtml {
  114. constructor(uid) {
  115. super({
  116. url: 'https://www.camamba.com/user_level.php',
  117. params: { uid: this.uid },
  118. resultTransformer: (response, request) => {
  119. const html = response.html;
  120.  
  121. let name = null, level = null;
  122.  
  123. const nameElement = html.querySelector('b');
  124. if (nameElement) {
  125. name = nameElement.textContent;
  126. }
  127.  
  128. const levelElement = html.querySelector('font.xxltext');
  129. if (levelElement) {
  130. const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
  131. if (levelMatch) {
  132. level = Number.parseInt(levelMatch);
  133. }
  134. }
  135.  
  136. return new UserLevel({ uid: request.params.uid, name, level, timeStamp: new Date().getTime() });
  137. }
  138. });
  139. }
  140.  
  141. /**
  142. * @returns {Promise<UserLevel>}
  143. */
  144. async send() {
  145. return await super.send();
  146. }
  147.  
  148. /**
  149. * @param {number} uid
  150. * @returns {Promise<UserLevel>}
  151. */
  152. static async send(uid) {
  153. return await new UserLevelSearch(uid).send();
  154. }
  155. }
  156.  
  157. class User {
  158. /** @param {UserParams} param0 */
  159. constructor({
  160. name, uid = 0, gender = null, age = null,
  161. longitude = null, latitude = null, location = null, distanceKM = null,
  162. isReal = null, hasPremium = null, hasSuper = null, isPerma = null,
  163. isOnline = null, room = null, lastSeen = null, regDate = null,
  164. dateToHumanReadable = (date) => date ?
  165. date.toLocaleString('de-DE', { timeStyle: "medium", dateStyle: "short", timeZone: 'CET' }) : '',
  166. }) {
  167. /** @type {string} */
  168. this.name = String(name);
  169. /** @type {number?} */
  170. this.uid = uid;
  171. /** @type {'male'|'female'|'couple'?} */
  172. this.gender = gender;
  173. /** @type {number?} */
  174. this.age = age;
  175.  
  176. /** @type {number?} */
  177. this.longitude = longitude;
  178. /** @type {number?} */
  179. this.latitude = latitude;
  180. /** @type {string?} */
  181. this.location = location;
  182. /** @type {number?} */
  183. this.distanceKM = distanceKM;
  184.  
  185. /** @type {boolean?} */
  186. this.isReal = isReal;
  187. /** @type {boolean?} */
  188. this.hasPremium = hasPremium;
  189. /** @type {boolean?} */
  190. this.hasSuper = hasSuper;
  191. /** @type {boolean?} */
  192. this.isPerma = isPerma;
  193.  
  194. /** @type {boolean?} */
  195. this.isOnline = isOnline;
  196. /** @type {string?} */
  197. this.room = room;
  198. /** @type {Date?} */
  199. this.lastSeen = lastSeen;
  200. /** @type {Date?} */
  201. this.regDate = regDate;
  202.  
  203. /** @type {string[]} */
  204. this.prints = [];
  205. /** @type {string[]} */
  206. this.ipList = [];
  207. /** @type {number?} */
  208. this.scorePassword = null;
  209. /** @type {number?} */
  210. this.scoreFinal = null;
  211. /** @type {number} */
  212. this.guessLogTS = null;
  213.  
  214. /** @type {(date: Date) => string} */
  215. this.dateToHumanReadable = dateToHumanReadable;
  216.  
  217. /** @type {number?} */
  218. this.level = null;
  219. /** @type {number} */
  220. this.levelTS = null;
  221.  
  222. /** @type {string[]} */
  223. this.galleryData = [];
  224. /** @type {number} */
  225. this.galleryDataTS = null;
  226. }
  227.  
  228. /** @type {string} @readonly */
  229. get lastSeenHumanReadable() {
  230. return this.dateToHumanReadable(this.lastSeen);
  231. }
  232.  
  233. /** @type {string} @readonly */
  234. get regDateHumanReadable() {
  235. return this.dateToHumanReadable(this.regDate);
  236. }
  237.  
  238. get galleryAsImgElements() {
  239. if (!this.galleryData) {
  240. return [];
  241. }
  242.  
  243. return this.galleryData.map(data => Object.assign(document.createElement('img'), {
  244. src: data.dataURI
  245. }));
  246. }
  247.  
  248. async updateGalleryHref() {
  249. const pictureLinks = (await HttpRequestHtml.send({
  250. url: "https://www.camamba.com/profile_view.php",
  251. params: Object.assign(
  252. { m: 'gallery' },
  253. this.uid ? { uid: this.uid } : { user: this.name }
  254. ),
  255.  
  256. pageNr: 0,
  257. pagesMaxCount: 500,
  258.  
  259. resultTransformer: (response) => {
  260. const hrefList = [...response.html.querySelectorAll("img.picborder")].map(img => img.src);
  261. return hrefList.map(href => href.slice(0, 0 - ".s.jpg".length) + ".l.jpg");
  262. },
  263. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  264. return lastResult.length >= 15;
  265. },
  266. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  267. })).flat();
  268.  
  269. this.galleryData = pictureLinks.map(href => ({ href }));
  270. this.galleryDataTS = new Date().getTime();
  271. }
  272.  
  273. async updateGalleryData(includeUpdateOfHref = true) {
  274. if (includeUpdateOfHref) {
  275. await this.updateGalleryHref();
  276. }
  277.  
  278. const readGalleryData = this.galleryData.map(href => (async () => {
  279. const dataURI = await HttpRequestBlob.send({ url: href });
  280. return new GalleryImage({ dataURI, href });
  281. })());
  282.  
  283. this.galleryData = await Promise.all(readGalleryData);
  284. this.galleryDataTS = new Date().getTime();
  285. }
  286.  
  287. async updateLevel() {
  288. const { level, timeStamp } = await UserLevelSearch.send(this.uid);
  289. this.level = level;
  290. this.levelTS = timeStamp;
  291. }
  292.  
  293. async updateGuessLog() {
  294. /** @type {GuessLog} */
  295. const guessLog = await GuessLogSearch.send(this.name);
  296. this.guessLogTS = new Date().getTime();
  297.  
  298. this.ipList = guessLog.ipList;
  299. this.prints = guessLog.prints;
  300. this.scorePassword = guessLog.scorePassword;
  301. this.scoreFinal = guessLog.scoreFinal;
  302. }
  303. }
  304.  
  305. class UserSearch extends HttpRequestHtml {
  306. /** @param {UserSearchParams} param0 */
  307. constructor({
  308. name = '',
  309. gender = 'any',
  310. isOnline = null,
  311. hasReal = null,
  312. hasPremium = null,
  313. hasSuper = null,
  314. hasPicture = null,
  315. isSortByRegDate = null,
  316. isSortByDistance = null,
  317. isShowAll = null,
  318. pageNr = 1,
  319. pagesMaxCount = 1,
  320. keepInCacheTimoutMs
  321. } = {}) {
  322. super({
  323. url: 'https://www.camamba.com/search.php',
  324. params: Object.assign(
  325. {
  326. nick: name,
  327. gender: gender.toLowerCase(),
  328. page: Math.max(pageNr - 1, 0),
  329. },
  330. Object.fromEntries(Object.entries({
  331. online: isOnline,
  332. isreal: hasReal,
  333. isprem: hasPremium,
  334. issuper: hasSuper,
  335. picture: hasPicture,
  336. sortreg: isSortByRegDate,
  337. byDistance: isSortByDistance,
  338. showall: isShowAll,
  339. })
  340. .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
  341. .map(([k, v]) => ([[k], v ? 1 : 0])))
  342. ),
  343. pageNr: Math.max(pageNr, 1),
  344. pagesMaxCount: Math.max(pagesMaxCount, 1),
  345. keepInCacheTimoutMs,
  346.  
  347. resultTransformer: (response) => {
  348. /** @type {Array<User>} */
  349. const users = [];
  350.  
  351. for (const tdNode of response.html.querySelectorAll('.searchSuper td:nth-child(2), .searchNormal td:nth-child(2)')) {
  352. const innerHTML = tdNode.innerHTML;
  353. const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML);
  354. const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);
  355. if (!uidMatch || !nameMatch) {
  356. break;
  357. }
  358.  
  359. const user = new User({
  360. name: nameMatch[1],
  361. uid: Number.parseInt(uidMatch[1]),
  362. isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
  363. hasPremium: /<a href="\/premium.php">/.test(innerHTML),
  364. hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
  365. isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
  366. });
  367.  
  368. // Längengrad, Breitengrad, Ortsname
  369. const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
  370. if (locationMatch) {
  371. user.longitude = Number.parseFloat(locationMatch[1]);
  372. user.latitude = Number.parseFloat(locationMatch[2]);
  373. user.location = locationMatch[3];
  374. }
  375.  
  376. // Entfernung in km
  377. const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
  378. if (distanceMatch) {
  379. user.distanceKM = parseInt(distanceMatch[1]);
  380. }
  381.  
  382. // Geschlecht und Alter
  383. const genderAgeMatch = /(male|female|couple),\s(\d{1,4})(?:<br>){2}Online/.exec(innerHTML);
  384. if (genderAgeMatch) {
  385. user.gender = genderAgeMatch[1];
  386. user.age = genderAgeMatch[2];
  387. }
  388.  
  389. // zuletzt Online
  390. if (user.isOnline) {
  391. user.lastSeen = new Date();
  392. } else {
  393. const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
  394. if (lastSeenMatch) {
  395. const value = parseInt(lastSeenMatch[1]);
  396.  
  397. const factorToMillis = {
  398. 'minutes': 1000 * 60,
  399. 'hours': 1000 * 60 * 60,
  400. 'days': 1000 * 60 * 60 * 24,
  401. }[lastSeenMatch[2]];
  402.  
  403. user.lastSeen = new Date(Date.now() - value * factorToMillis);
  404. }
  405. }
  406.  
  407. // Raumname
  408. const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
  409. if (roomMatch) {
  410. user.room = roomMatch[1];
  411. }
  412.  
  413. // regDate
  414. const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
  415. if (regDateMatch) {
  416. const regDateDay = regDateMatch[1];
  417. const regDateMonth = regDateMatch[2];
  418. const regDateYear = regDateMatch[3];
  419. const regDateHour = regDateMatch[4];
  420. const regDateMinute = regDateMatch[5];
  421. const regDateSecond = regDateMatch[6];
  422. user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
  423. }
  424.  
  425. users.push(user);
  426. }
  427.  
  428. return users;
  429. },
  430.  
  431. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  432. return lastResult.length >= 50;
  433. },
  434.  
  435. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  436. });
  437. }
  438.  
  439. /** @returns {Promise<User[]>} */
  440. async send() {
  441. return (await super.send()).flat();
  442. }
  443.  
  444. /**
  445. * @param {UserSearchParams} param0
  446. * @returns {Promise<User[]>}
  447. */
  448. static async send(param0) {
  449. return await new UserSearch(param0).send();
  450. }
  451. }