Camamba Users Search Library

fetches Users

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

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