Camamba Users Search Library

fetches Users

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/446634/1063403/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=1063401
  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. class User {
  96. /** @param {UserParams} param0 */
  97. constructor({
  98. name, uid = 0, gender = null, age = null,
  99. longitude = null, latitude = null, location = null, distanceKM = null,
  100. isReal = null, hasPremium = null, hasSuper = null, isPerma = null,
  101. isOnline = null, room = null, lastSeen = null, regDate = null,
  102. dateToHumanReadable = (date) => date ?
  103. date.toLocaleString('de-DE', { timeStyle: "medium", dateStyle: "short", timeZone: 'CET' }) : '',
  104. }) {
  105. /** @type {string} */
  106. this.name = String(name);
  107. /** @type {number?} */
  108. this.uid = uid;
  109. /** @type {'male'|'female'|'couple'?} */
  110. this.gender = gender;
  111. /** @type {number?} */
  112. this.age = age;
  113.  
  114. /** @type {number?} */
  115. this.longitude = longitude;
  116. /** @type {number?} */
  117. this.latitude = latitude;
  118. /** @type {string?} */
  119. this.location = location;
  120. /** @type {number?} */
  121. this.distanceKM = distanceKM;
  122.  
  123. /** @type {boolean?} */
  124. this.isReal = isReal;
  125. /** @type {boolean?} */
  126. this.hasPremium = hasPremium;
  127. /** @type {boolean?} */
  128. this.hasSuper = hasSuper;
  129. /** @type {boolean?} */
  130. this.isPerma = isPerma;
  131.  
  132. /** @type {boolean?} */
  133. this.isOnline = isOnline;
  134. /** @type {string?} */
  135. this.room = room;
  136. /** @type {Date?} */
  137. this.lastSeen = lastSeen;
  138. /** @type {Date?} */
  139. this.regDate = regDate;
  140.  
  141. /** @type {string[]} */
  142. this.prints = [];
  143. /** @type {string[]} */
  144. this.ipList = [];
  145. /** @type {number?} */
  146. this.scorePassword = null;
  147. /** @type {number?} */
  148. this.scoreFinal = null;
  149. /** @type {number} */
  150. this.guessLogTS = null;
  151.  
  152. /** @type {(date: Date) => string} */
  153. this.dateToHumanReadable = dateToHumanReadable;
  154.  
  155. /** @type {number?} */
  156. this.level = null;
  157. /** @type {number} */
  158. this.levelTS = null;
  159.  
  160. /** @type {string[]} */
  161. this.galleryData = [];
  162. /** @type {number} */
  163. this.galleryDataTS = null;
  164. }
  165.  
  166. /** @type {string} @readonly */
  167. get lastSeenHumanReadable() {
  168. return this.dateToHumanReadable(this.lastSeen);
  169. }
  170.  
  171. /** @type {string} @readonly */
  172. get regDateHumanReadable() {
  173. return this.dateToHumanReadable(this.regDate);
  174. }
  175.  
  176. get galleryAsImgElements() {
  177. if (!this.galleryData) {
  178. return [];
  179. }
  180.  
  181. return this.galleryData.map(data => Object.assign(document.createElement('img'), {
  182. src: data.dataURI
  183. }));
  184. }
  185.  
  186. async updateGalleryData() {
  187. const pictureLinks = (await HttpRequestHtml.send({
  188. url: "https://www.camamba.com/profile_view.php",
  189. params: Object.assign(
  190. { m: 'gallery' },
  191. this.uid ? { uid: this.uid } : { user: this.name }
  192. ),
  193.  
  194. pageNr: 1,
  195. pagesMaxCount: 500,
  196.  
  197. resultTransformer: (response) => {
  198. const hrefList = [...response.html.querySelectorAll("img.picborder")].map(img => img.src);
  199. return hrefList.map(href => href.slice(0, 0 - ".s.jpg".length) + ".l.jpg");
  200. },
  201. hasNextPage: (_resp, _httpRequestHtml, lastResult) => lastResult.length >= 15,
  202. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  203. })).flat();
  204.  
  205. const readGalleryData = pictureLinks.map(href => (async () => {
  206. const dataURI = await HttpRequestBlob.send({ href });
  207. return new GalleryImage({ dataURI, href });
  208. })());
  209.  
  210. this.galleryData = await Promise.all(readGalleryData);
  211. this.galleryDataTS = new Date().getTime();
  212. }
  213.  
  214. async updateLevel() {
  215. this.level = await HttpRequestHtml.send({
  216. url: 'https://www.camamba.com/user_level.php',
  217. params: { uid: this.uid },
  218. resultTransformer: (response) => {
  219. const levelElement = response.html.querySelector('font.xxltext');
  220. if (levelElement) {
  221. const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
  222. if (levelMatch) {
  223. return parseInt(levelMatch);
  224. }
  225. }
  226. return null;
  227. }
  228. });
  229. this.levelTS = new Date().getTime();
  230. }
  231.  
  232. async updateGuessLog() {
  233. /** @type {GuessLog} */
  234. const guessLog = await GuessLogSearch.send(this.name);
  235. this.guessLogTS = new Date().getTime();
  236.  
  237. this.ipList = guessLog.ipList;
  238. this.prints = guessLog.prints;
  239. this.scorePassword = guessLog.scorePassword;
  240. this.scoreFinal = guessLog.scoreFinal;
  241. }
  242. }
  243.  
  244. class UserSearch extends HttpRequestHtml {
  245. /** @param {UserSearchParams} param0 */
  246. constructor({
  247. name = '',
  248. gender = 'any',
  249. isOnline = null,
  250. hasReal = null,
  251. hasPremium = null,
  252. hasSuper = null,
  253. hasPicture = null,
  254. isSortByRegDate = null,
  255. isSortByDistance = null,
  256. isShowAll = null,
  257. pageNr = 1,
  258. pagesMaxCount = 1,
  259. keepInCacheTimoutMs
  260. } = {}) {
  261. super({
  262. url: 'https://www.camamba.com/search.php',
  263. params: Object.assign(
  264. {
  265. nick: name,
  266. gender: gender.toLowerCase(),
  267. page: Math.max(pageNr - 1, 0),
  268. },
  269. Object.fromEntries(Object.entries({
  270. online: isOnline,
  271. isreal: hasReal,
  272. isprem: hasPremium,
  273. issuper: hasSuper,
  274. picture: hasPicture,
  275. sortreg: isSortByRegDate,
  276. byDistance: isSortByDistance,
  277. showall: isShowAll,
  278. })
  279. .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
  280. .map(([k, v]) => ([[k], v ? 1 : 0])))
  281. ),
  282. pageNr: Math.max(pageNr, 1),
  283. pagesMaxCount: Math.max(pagesMaxCount, 1),
  284. keepInCacheTimoutMs,
  285.  
  286. resultTransformer: (response) => {
  287. /** @type {Array<User>} */
  288. const users = [];
  289.  
  290. for (const tdNode of response.html.querySelectorAll('.searchSuper td:nth-child(2), .searchNormal td:nth-child(2)')) {
  291. const innerHTML = tdNode.innerHTML;
  292. const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML);
  293. const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);
  294. if (!uidMatch || !nameMatch) {
  295. break;
  296. }
  297.  
  298. const user = new User({
  299. name: nameMatch[1],
  300. uid: Number.parseInt(uidMatch[1]),
  301. isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
  302. hasPremium: /<a href="\/premium.php">/.test(innerHTML),
  303. hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
  304. isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
  305. });
  306.  
  307. // Längengrad, Breitengrad, Ortsname
  308. const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
  309. if (locationMatch) {
  310. user.longitude = Number.parseFloat(locationMatch[1]);
  311. user.latitude = Number.parseFloat(locationMatch[2]);
  312. user.location = locationMatch[3];
  313. }
  314.  
  315. // Entfernung in km
  316. const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
  317. if (distanceMatch) {
  318. user.distanceKM = parseInt(distanceMatch[1]);
  319. }
  320.  
  321. // Geschlecht und Alter
  322. const genderAgeMatch = /(male|female|couple),\s(\d{1,3})(?:<br>){2}Online/.exec(innerHTML);
  323. if (genderAgeMatch) {
  324. user.gender = genderAgeMatch[1];
  325. user.age = genderAgeMatch[2];
  326. }
  327.  
  328. // zuletzt Online
  329. if (user.isOnline) {
  330. user.lastSeen = new Date();
  331. } else {
  332. const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
  333. if (lastSeenMatch) {
  334. const value = parseInt(lastSeenMatch[1]);
  335.  
  336. const factorToMillis = {
  337. 'minutes': 1000 * 60,
  338. 'hours': 1000 * 60 * 60,
  339. 'days': 1000 * 60 * 60 * 24,
  340. }[lastSeenMatch[2]];
  341.  
  342. user.lastSeen = new Date(Date.now() - value * factorToMillis);
  343. }
  344. }
  345.  
  346. // Raumname
  347. const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
  348. if (roomMatch) {
  349. user.room = roomMatch[1];
  350. }
  351.  
  352. // regDate
  353. const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
  354. if (regDateMatch) {
  355. const regDateDay = regDateMatch[1];
  356. const regDateMonth = regDateMatch[2];
  357. const regDateYear = regDateMatch[3];
  358. const regDateHour = regDateMatch[4];
  359. const regDateMinute = regDateMatch[5];
  360. const regDateSecond = regDateMatch[6];
  361. user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
  362. }
  363.  
  364. users.push(user);
  365. }
  366.  
  367. return users;
  368. },
  369.  
  370. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  371. return lastResult.length >= 50;
  372. },
  373.  
  374. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  375. });
  376. }
  377.  
  378. /** @returns {Promise<User[]>} */
  379. async send() {
  380. return (await super.send()).flat();
  381. }
  382.  
  383. /**
  384. * @param {UserSearchParams} param0
  385. * @returns {Promise<User[]>}
  386. */
  387. static async send(param0) {
  388. return await new UserSearch(param0).send();
  389. }
  390. }