Camamba Users Search Library

fetches Users

目前为 2022-06-19 提交的版本。查看 最新版本

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

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