Camamba User

User represents a camamba User, UserSearch represents a search-request for one or many users

当前为 2020-06-20 提交的版本,查看 最新版本

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

  1. // ==UserScript==
  2. // @name Camamba User
  3. // @namespace dannysaurus.camamba
  4. //
  5. // @include http://www.camamba.com/*
  6. // @include https://www.camamba.com/*
  7. // @include http://www.de.camamba.com/*
  8. // @include https://www.de.camamba.com/*
  9. //
  10. // @connect camamba.com
  11. // @grant GM_xmlhttpRequest
  12. //
  13. // @require https://greasyfork.org/scripts/405143-simplecache/code/SimpleCache.js
  14. // @require https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js
  15. // @require https://greasyfork.org/scripts/391854-enum/code/Enum.js
  16. //
  17. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
  18. //
  19. // @version 0.1
  20. // @license MIT License
  21. // @description User represents a camamba User, UserSearch represents a search-request for one or many users
  22. // @author Gerrit Höhle
  23. // ==/UserScript==
  24.  
  25. /* jslint esnext: true */
  26. /* globals SimpleCache, HttpRequest, HttpRequestHtml, Enum, $, jQuery */
  27. const User = (() => {
  28. 'use strict';
  29. const GENDERS = class GENDER extends Enum {}.init({ ANY: 'any' , FEMALE: 'female' , MALE: 'male', COUPLE: 'couple' });
  30.  
  31. return class User {
  32. constructor({ uid, nick, age, gender, location, distanceKm, latitudeDec, longitudeDec, isOnline, lastSeen, room, roomId, isReal, isPremium, isSuper, userLevel, timeStamp = new Date() } = {}) {
  33. Object.assign(this, { uid, nick, age, _gender: gender, location, distanceKm, latitudeDec, longitudeDec, isOnline, lastSeen, room, roomId, isReal, isPremium, isSuper, userLevel, timeStamp });
  34. }
  35.  
  36. get gender() {
  37. return this._gender instanceof GENDERS ? this._gender : GENDERS.ANY;
  38. }
  39.  
  40. set gender(value) {
  41. if (value instanceof GENDERS) {
  42. this._gender = value;
  43. } else {
  44. this._gender = [ ...GENDERS ].find(gender => gender.toString() === String(value)) || GENDERS.ANY;
  45. }
  46. }
  47.  
  48. static get GENDERS() {
  49. return GENDERS;
  50. }
  51. };
  52. })();
  53.  
  54. const UserSearch = (() => {
  55. const CONST = {
  56. URL_SEARCH: 'https://www.camamba.com/search.php',
  57. URL_USER_LEVEL: 'https://www.camamba.com/user_level.php'
  58. };
  59.  
  60. const UserControllerInternal = (() => {
  61. const GENDERS = User.GENDERS;
  62.  
  63. const getTextNodeValues = node => {
  64. if (node.nodeType === Node.TEXT_NODE) {
  65. const nodeValue = node.nodeValue.replace(/"/,'');
  66. return (/\S/.test(nodeValue)) ? [ nodeValue ] : [];
  67. }
  68. return Array.from(node.childNodes).flatMap(node => getTextNodeValues(node));
  69. };
  70.  
  71. const fetchJsLinks = (html, user) => {
  72. const jsLinks = Object.assign( {}, ...[ 'openProfile', 'sendMail', 'openChat', 'openMap' ].map(fncName => {
  73. const linkElement = $(html).find(`a[href*="javascript:${fncName}("]`);
  74. if (linkElement.length > 0) {
  75. const href = linkElement.attr('href') || '';
  76. const fncParam = ((href.match(/\((.+)\)/) || [])[1] || '').replace(/['"]/g,'');
  77. const innerHtml = (linkElement.html() || '').trim();
  78.  
  79. return { [fncName]: { href, fncParam, html: innerHtml } };
  80. }
  81. }));
  82.  
  83. if (typeof jsLinks.sendMail !== 'undefined') {
  84. user.uid = Number.parseInt(jsLinks.sendMail.fncParam);
  85. }
  86.  
  87. if (typeof jsLinks.openProfile !== 'undefined') {
  88. user.nick = jsLinks.openProfile.fncParam;
  89. }
  90.  
  91. if (typeof jsLinks.openChat !== 'undefined') {
  92. user.roomId = jsLinks.openChat.fncParam;
  93. user.room = jsLinks.openChat.html;
  94. }
  95.  
  96. if (typeof jsLinks.openMap !== 'undefined') {
  97. const matchCoords = jsLinks.openMap.fncParam.match(/(-?\d{1,3}\.\d{8}),(-?\d{1,2}\.\d{8})/);
  98. if (matchCoords !== null) {
  99. user.longitudeDec = Number.parseFloat(matchCoords[1]);
  100. user.latitudeDec = Number.parseFloat(matchCoords[2]);
  101. }
  102. user.location = jsLinks.openMap.html;
  103. }
  104. };
  105.  
  106. const fetchTextNodes = (html, user) => {
  107. const textNodes = getTextNodeValues(html);
  108.  
  109. for (let gender of [ GENDERS.FEMALE, GENDERS.MALE, GENDERS.COUPLE ].map(enm => enm.text)) {
  110. const match = textNodes[1].match(new RegExp(`^\\W*\(${gender}\),\\s\(\\d{1,3}\)`));
  111. if (match !== null) {
  112.  
  113. user.age = Number.parseInt(match[2]);
  114. user.gender = match[1];
  115. break;
  116. }
  117. }
  118.  
  119. const matches = textNodes[2].match(/Online\s((\d{1,4})\s(minutes|hours|days)\s)?(ago|now|never)/);
  120. if (matches !== null) {
  121. const term = matches[4];
  122. if (term === 'never') {
  123. user.lastSeen = new Date(0);
  124. user.isOnline = false;
  125. } else {
  126. const timeStampShort = new Date(user.timeStamp.getFullYear(), user.timeStamp.getMonth(), user.timeStamp.getDate(), user.timeStamp.getHours(), user.timeStamp.getMinutes());
  127.  
  128. if (term === 'ago') {
  129. user.lastSeen = timeStampShort;
  130. user.isOnline = false;
  131.  
  132. const value = Number.parseInt(matches[2]);
  133. const unit = matches[3];
  134. switch (unit) {
  135. case 'minutes':
  136. user.lastSeen.setMinutes(user.lastSeen.getMinutes() - value);
  137. break;
  138. case 'hours':
  139. user.lastSeen.setMinutes(0);
  140. user.lastSeen.setHours(user.lastSeen.getHours() - value);
  141. break;
  142. case 'days':
  143. user.lastSeen.setMinutes(0);
  144. user.lastSeen.setHours(0);
  145. user.lastSeen.setDate(user.lastSeen.getDate() - value);
  146. break;
  147. }
  148. } else if (term === 'now') {
  149. user.lastSeen = timeStampShort;
  150. user.isOnline = true;
  151. }
  152. }
  153. }
  154.  
  155. for (let txt of textNodes.slice(2)) {
  156. const distanceMatch = txt.match(/(\d{0,2},?\d{1,3})\skm\sfrom\syou/);
  157. if (distanceMatch !== null) {
  158.  
  159. const distanceKm = Number.parseInt(distanceMatch[1].replace(/\D/g, ''));
  160. if (Number.isInteger(distanceKm)) {
  161.  
  162. user.distanceKm = distanceKm;
  163. }
  164. }
  165. }
  166. };
  167.  
  168. const fetchImages = (html, user) => {
  169. for (let [property, imageName] of Object.entries({ isPremium: 'premium.png', isSuper: 'super_premium.png', isReal: 'real.png' })) {
  170. user[property] = $(html).find(`img[src$='/gfx/${imageName}']`).length > 0;
  171. }
  172. };
  173.  
  174. const fetchCustom = (html, user) => {
  175. const embedRankSrc = $(html).find("embed[src^='/levelRank.swf?displayRank']").attr('src') || '';
  176. const match = embedRankSrc.match(/displayRank=(\d{1,3})/);
  177. if (match !== null) {
  178. user.userLevel = Number.parseInt(match[1]);
  179. }
  180. };
  181.  
  182. return class UserControllerInternal {
  183. constructor(user = new User()) {
  184. this.user = user;
  185. }
  186.  
  187. parseHtml({ html = '', url = '' } = {}) {
  188. if (url.includes(CONST.URL_SEARCH)) {
  189. fetchJsLinks(html, this.user);
  190. fetchTextNodes(html, this.user);
  191. fetchImages(html, this.user);
  192.  
  193. } else if (url.includes(CONST.URL_USER_LEVEL)) {
  194. const matchUid = url.match(/uid=(\d{1,7})/);
  195. if (matchUid !== null) {
  196. this.user.uid = Number.parseInt(matchUid[1]);
  197. }
  198.  
  199. const nick = $(html).find('div.popupcontent').find('div.bbottom').text();
  200. if (nick) {
  201. this.user.nick = nick;
  202. }
  203.  
  204. fetchCustom(html, this.user);
  205. }
  206.  
  207. return this;
  208. }
  209.  
  210. static parseHtml({ html, url, user = new User() } = {}) {
  211. return new UserControllerInternal(user).parseHtml({ html, url });
  212. }
  213. };
  214. })();
  215.  
  216. const createUrlParamsFromUserSearch = userSearch => Object.fromEntries(
  217. Object.entries({
  218. nick: 'nick',
  219. gender: 'gender',
  220. page: 'page',
  221. isOnline: 'online',
  222. hasPicture: 'picture',
  223. isPremium: 'isprem',
  224. isReal: 'isreal',
  225. isSuper: 'issuper'
  226. })
  227. .filter(([ propName, paramName ]) => userSearch[propName])
  228. .map(([ propName, paramName ]) => {
  229. let paramValue = userSearch[propName];
  230. paramValue = (typeof paramValue === 'boolean') ? (paramValue ? 1 : 0) : String(paramValue).replace(/\s/g, '+');
  231. return [ paramName, paramValue ];
  232. })
  233. );
  234.  
  235. const executeUserSearch = (userSearch) => {
  236. return HttpRequestHtml.send({
  237. url: CONST.URL_SEARCH, params: createUrlParamsFromUserSearch(userSearch)
  238. }).then((response) => {
  239. const userNodes = $(response.html).find('.searchNormal, .searchSuper').get();
  240. const users = userNodes.map(node => UserControllerInternal.parseHtml({ html: node, url: response.finalUrl }).user);
  241. return users;
  242. });
  243. };
  244.  
  245. return class UserSearch {
  246. constructor({ nick = '', gender = User.GENDERS.ANY, isOnline = false, hasPicture = false, isPremium = false, isReal = false, isSuper = false, page = 0 } = {}) {
  247. Object.assign(this, { nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper, page });
  248. }
  249.  
  250. execute() {
  251. return executeUserSearch(this).then(users => Promise.all(
  252. users.map(user => HttpRequestHtml.send({
  253. url: CONST.URL_USER_LEVEL, params: { uid: user.uid }
  254. }).then(response => UserControllerInternal.parseHtml({ html: response.html, user: user, url: response.finalUrl }).user))
  255. ));
  256. }
  257.  
  258. static execute(...args) {
  259. return new UserSearch(...args).execute();
  260. }
  261.  
  262. static byUid(uid) {
  263. return HttpRequestHtml.send({
  264. url: CONST.URL_USER_LEVEL, params: { uid: uid }
  265. }).then(response => UserControllerInternal.parseHtml({ html: response.html, url: response.finalUrl }).user).then(user => {
  266. if (!(user && user.nick)) {
  267. return Promise.reject(new Error(`No result for User with uid '${uid}'.`));
  268. }
  269. return executeUserSearch(new UserSearch({ nick: user.nick }));
  270. }).then(users => users.find(user => user.uid === uid ));
  271. }
  272.  
  273. static get GENDERS() {
  274. return User.GENDERS;
  275. }
  276. };
  277. })();