Camamba Users Search Library

fetches Users

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/446634/1063391/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=1063178
  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: 0,
  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) => {
  202. return lastResult.length >= 15;
  203. },
  204. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  205. })).flat();
  206.  
  207. const readGalleryData = pictureLinks.map(href => (async () => {
  208. const dataURI = await HttpRequestBlob.send({ href });
  209. return new GalleryImage({ dataURI, href });
  210. })());
  211.  
  212. this.galleryData = await Promise.all(readGalleryData);
  213. this.galleryDataTS = new Date().getTime();
  214. }
  215.  
  216. async updateLevel() {
  217. this.level = await HttpRequestHtml.send({
  218. url: 'https://www.camamba.com/user_level.php',
  219. params: { uid: this.uid },
  220. resultTransformer: (response) => {
  221. const levelElement = response.html.querySelector('font.xxltext');
  222. if (levelElement) {
  223. const levelMatch = /\d{1,3}/.exec(levelElement.textContent);
  224. if (levelMatch) {
  225. return parseInt(levelMatch);
  226. }
  227. }
  228. return null;
  229. }
  230. });
  231. this.levelTS = new Date().getTime();
  232. }
  233.  
  234. async updateGuessLog() {
  235. /** @type {GuessLog} */
  236. const guessLog = await GuessLogSearch.send(this.name);
  237. this.guessLogTS = new Date().getTime();
  238.  
  239. this.ipList = guessLog.ipList;
  240. this.prints = guessLog.prints;
  241. this.scorePassword = guessLog.scorePassword;
  242. this.scoreFinal = guessLog.scoreFinal;
  243. }
  244. }
  245.  
  246. class UserSearch extends HttpRequestHtml {
  247. /** @param {UserSearchParams} param0 */
  248. constructor({
  249. name = '',
  250. gender = 'any',
  251. isOnline = null,
  252. hasReal = null,
  253. hasPremium = null,
  254. hasSuper = null,
  255. hasPicture = null,
  256. isSortByRegDate = null,
  257. isSortByDistance = null,
  258. isShowAll = null,
  259. pageNr = 1,
  260. pagesMaxCount = 1,
  261. keepInCacheTimoutMs
  262. } = {}) {
  263. super({
  264. url: 'https://www.camamba.com/search.php',
  265. params: Object.assign(
  266. {
  267. nick: name,
  268. gender: gender.toLowerCase(),
  269. page: Math.max(pageNr - 1, 0),
  270. },
  271. Object.fromEntries(Object.entries({
  272. online: isOnline,
  273. isreal: hasReal,
  274. isprem: hasPremium,
  275. issuper: hasSuper,
  276. picture: hasPicture,
  277. sortreg: isSortByRegDate,
  278. byDistance: isSortByDistance,
  279. showall: isShowAll,
  280. })
  281. .filter(([_k, v]) => typeof v !== 'undefined' && v !== null)
  282. .map(([k, v]) => ([[k], v ? 1 : 0])))
  283. ),
  284. pageNr: Math.max(pageNr, 1),
  285. pagesMaxCount: Math.max(pagesMaxCount, 1),
  286. keepInCacheTimoutMs,
  287.  
  288. resultTransformer: (response) => {
  289. /** @type {Array<User>} */
  290. const users = [];
  291.  
  292. for (const tdNode of response.html.querySelectorAll('.searchSuper td:nth-child(2), .searchNormal td:nth-child(2)')) {
  293. const innerHTML = tdNode.innerHTML;
  294. const uidMatch = /<a\s+?href=["']javascript:sendMail\(["'](\d{1,8})["']\)/.exec(innerHTML);
  295. const nameMatch = /<a\s+?href=["']javascript:openProfile\(["'](.+?)["']\)/.exec(innerHTML);
  296. if (!uidMatch || !nameMatch) {
  297. break;
  298. }
  299.  
  300. const user = new User({
  301. name: nameMatch[1],
  302. uid: Number.parseInt(uidMatch[1]),
  303. isReal: /<img src="\/gfx\/real.png"/.test(innerHTML),
  304. hasPremium: /<a href="\/premium.php">/.test(innerHTML),
  305. hasSuper: /<img src="\/gfx\/super_premium.png"/.test(innerHTML),
  306. isOnline: /Online\snow(\s\in|,\snot in chat)/.test(innerHTML),
  307. });
  308.  
  309. // Längengrad, Breitengrad, Ortsname
  310. const locationMatch = /<a\s+?href="javascript:openMap\((-?\d{1,3}\.\d{8}),(-?\d{1,3}\.\d{8})\);">(.+?)<\/a>/.exec(innerHTML);
  311. if (locationMatch) {
  312. user.longitude = Number.parseFloat(locationMatch[1]);
  313. user.latitude = Number.parseFloat(locationMatch[2]);
  314. user.location = locationMatch[3];
  315. }
  316.  
  317. // Entfernung in km
  318. const distanceMatch = /(\d{1,5})\skm\sfrom\syou/.exec(innerHTML);
  319. if (distanceMatch) {
  320. user.distanceKM = parseInt(distanceMatch[1]);
  321. }
  322.  
  323. // Geschlecht und Alter
  324. const genderAgeMatch = /(male|female|couple),\s(\d{1,3})(?:<br>){2}Online/.exec(innerHTML);
  325. if (genderAgeMatch) {
  326. user.gender = genderAgeMatch[1];
  327. user.age = genderAgeMatch[2];
  328. }
  329.  
  330. // zuletzt Online
  331. if (user.isOnline) {
  332. user.lastSeen = new Date();
  333. } else {
  334. const lastSeenMatch = /(\d{1,4})\s(minutes|hours|days)\sago/.exec(innerHTML);
  335. if (lastSeenMatch) {
  336. const value = parseInt(lastSeenMatch[1]);
  337.  
  338. const factorToMillis = {
  339. 'minutes': 1000 * 60,
  340. 'hours': 1000 * 60 * 60,
  341. 'days': 1000 * 60 * 60 * 24,
  342. }[lastSeenMatch[2]];
  343.  
  344. user.lastSeen = new Date(Date.now() - value * factorToMillis);
  345. }
  346. }
  347.  
  348. // Raumname
  349. const roomMatch = /(?:ago|now)\sin\s([\w\s]+?|p\d{1,8})<br>/.exec(innerHTML);
  350. if (roomMatch) {
  351. user.room = roomMatch[1];
  352. }
  353.  
  354. // regDate
  355. const regDateMatch = /(\d{2}).(\d{2}).(\d{4})\s(\d{1,2}):(\d{2}):(\d{2})/.exec(innerHTML);
  356. if (regDateMatch) {
  357. const regDateDay = regDateMatch[1];
  358. const regDateMonth = regDateMatch[2];
  359. const regDateYear = regDateMatch[3];
  360. const regDateHour = regDateMatch[4];
  361. const regDateMinute = regDateMatch[5];
  362. const regDateSecond = regDateMatch[6];
  363. user.regDate = new Date(regDateYear, regDateMonth - 1, regDateDay, regDateHour, regDateMinute, regDateSecond);
  364. }
  365.  
  366. users.push(user);
  367. }
  368.  
  369. return users;
  370. },
  371.  
  372. hasNextPage: (_resp, _httpRequestHtml, lastResult) => {
  373. return lastResult.length >= 50;
  374. },
  375.  
  376. paramsConfiguratorForPageNr: (params, pageNr) => ({ ...params, page: pageNr }),
  377. });
  378. }
  379.  
  380. /** @returns {Promise<User[]>} */
  381. async send() {
  382. return (await super.send()).flat();
  383. }
  384.  
  385. /**
  386. * @param {UserSearchParams} param0
  387. * @returns {Promise<User[]>}
  388. */
  389. static async send(param0) {
  390. return await new UserSearch(param0).send();
  391. }
  392. }