fetlife_all_members (ASL+role+status search)

greasemonkey script to find fetlife members when it's possible. Search by name, gender, role, age, location or status.

As of 2015-02-12. See the latest version.

  1. // ==UserScript==
  2. // @name fetlife_all_members (ASL+role+status search)
  3. // @namespace bewam.free.fr
  4. // @description greasemonkey script to find fetlife members when it's possible. Search by name, gender, role, age, location or status.
  5. // @include http*://fetlife.com/*
  6. // @version 1.8.5.20150212
  7. // @grant GM_addStyle
  8. // @run-at document-end
  9. // @require http://code.jquery.com/jquery-2.1.1.min.js
  10. // ==/UserScript==
  11. /*-----------------------------------*/
  12. const DEBUG = false;
  13. function debug() {
  14. if (DEBUG && console) {
  15. console.log.apply(console, arguments);
  16. }
  17. }
  18. /*-----------------------------------*/
  19. (function ($){
  20.  
  21. const STRING_USERS = 'div.user_in_list',
  22. STRING_VAF = 'viewAllForm',
  23. ARRAY_GENDER = ['M','F','CD/TV','MtF','FtM','TG','GF','GQ','IS','B','FEM'],
  24. ARRAY_GENDER_LABEL = ['Male','Female','CD/TV','Trans-MtF','Trans-FtM','Transgender','Gender Fluid','Genderqueer','Intersex','Butch','Femme'],
  25. ARRAY_ROLE = ['Dom','Domme','Switch','sub','Master','Mistress','slave','pet','kajira','kajirus','Top','Bottom','Sadist','Masochist','Sadomasochist','Ageplayer','Daddy','babygirl','babyboy','Mommy','brat','Primal','Fetishist','Kinkster','Hedonist','Vanilla','Unsure'],
  26. ARRAY_ROLE_LABEL = ARRAY_ROLE,
  27. ARRAY_INTO_STATUS = ['is into', 'is curious about'],
  28. ARRAY_INTO_ACTIVITY = ['giving', 'receiving', 'watching', 'wearing', 'watching others wear', 'everything to do with it']
  29. ;
  30.  
  31. var placeAfter = '#header_v2';
  32.  
  33. var next = '';
  34. /* balance columns */
  35. var altPos = 0,
  36. currentCount = 0
  37. ;
  38. var members = [], // user html storage
  39. /** NOTE mCache = ARRAY( { i: { [0]'name':'', [1]'age':XX, [2]'gender':WW, [3]'role':'', [4]'location':'', [5]url:'', [6]"hasAvatar":boolean } }) */
  40. mCache = [],
  41. listContainers = [] // columns where lists appear
  42. ;
  43. var userRegExp = new RegExp('([0-9]{2})('+ARRAY_GENDER.join('|')+')? ('+ARRAY_ROLE.join('|')+')?','i');
  44.  
  45. /** modified, value, default value*/
  46. var filters = {
  47. 'NameContains': [false,'',''],
  48. 'AgeMin': [false,'',''],
  49. 'AgeMax': [false,'',''],
  50. 'Gender': [false, [], []],
  51. 'Role': [false, [], []],
  52. 'LocContains': [false,'',''],
  53. 'IntoStatus': [false,'',''],
  54. 'IntoActivity': [false,'','']
  55. // has_avatar: @see function
  56. }
  57. var ajaxLocked = false,
  58. scriptLaunched = false,
  59. is_fetishes_page = /^\/fetishes/.test(unsafeWindow.location.pathname)
  60. ;
  61. /*-----------------------------------*/
  62. function init()
  63. {
  64. var nextPage = $('a.next_page'),
  65. previousPage = $('a.previous_page'),
  66. pageUsers = $(STRING_USERS),
  67. Ctrls = "#"+STRING_VAF+"Controls"
  68. ;
  69. /** a next_page link and a list of members are on current page ? go on */
  70. if( ( nextPage.length > 0 || previousPage.length > 0) && pageUsers.length > 0 )
  71. {
  72. setContainers(pageUsers)
  73. debug("listContainers: "+listContainers);
  74.  
  75. drawBlock();
  76.  
  77. $(Ctrls).click(function()
  78. {
  79. $('#'+STRING_VAF+'Content').toggle("slow")
  80. console.log(this)
  81. });
  82. setNext(nextPage);
  83. $('#'+STRING_VAF+'ButtonGo').click( function()
  84. {
  85. $(this).attr("disabled", 'true');
  86. launchSearch();
  87. });
  88. }
  89. };
  90. function launchSearch()
  91. {
  92. if( ! scriptLaunched )
  93. {
  94. $('#'+STRING_VAF+'ShowCount').html("loading ...")
  95. updateFilters();
  96. debug(filters);
  97. // TODO delete it when "?page=1"
  98. // lookPage();
  99. cleanPage();
  100. lookAhead(true);
  101. scriptLaunched = true;
  102. }
  103. }
  104. GM_addStyle(
  105. '#'+STRING_VAF+'Container { \
  106. background-color: rgba(255, 255, 255, 0.4);\
  107. color: white !important;/**/ \
  108. padding:5px;\
  109. min-height:15px !important;\
  110. vertical-align:middle;\
  111. } \
  112. #'+STRING_VAF+'Controls { \
  113. display: block;/**/ \
  114. min-height:15px !important;\
  115. }\
  116. #'+STRING_VAF+'Content { \
  117. display: none;/**/ \
  118. }\
  119. #'+STRING_VAF+'ButtonStop{ \
  120. display: none;\
  121. }\
  122. '
  123. );
  124.  
  125. function drawBlock(){
  126.  
  127. var options_gender = '',
  128. options_role = '',
  129. options_into_activity = '',
  130. options_into_status = ''
  131. ;
  132. $.each(ARRAY_GENDER, function(i,v){options_gender+='<option value="'+v+'" >'+ARRAY_GENDER_LABEL[i]+'</option>'});
  133. $.each(ARRAY_ROLE, function(i,v){options_role+='<option value="'+v+'" >'+ARRAY_ROLE_LABEL[i]+'</option>'});
  134. $.each(ARRAY_INTO_ACTIVITY, function(i,v){options_into_activity+='<option value="'+v+'" >'+ARRAY_INTO_ACTIVITY[i]+'</option>'});
  135. $.each(ARRAY_INTO_STATUS, function(i,v){options_into_status+='<option value="'+v+'" >'+ARRAY_INTO_STATUS[i]+'</option>'});
  136. var BLOCK = '<div id="'+STRING_VAF+'Container"> \
  137. <div id="'+STRING_VAF+'Controls">\
  138. <b>&gt;</b> \
  139. <u>view all members</u>\
  140. </div>\
  141. <div id="'+STRING_VAF+'Content"> \
  142. <u>filters</u> <br />\
  143. &nbsp;name: &nbsp;\
  144. <input id="'+STRING_VAF+'NameContains" type="text" class="filter"></input>\
  145. &nbsp;location: &nbsp;\
  146. <input id="'+STRING_VAF+'LocContains" type="text" class="filter" ></input>\
  147. \
  148. age: &nbsp;\
  149. min: <input id="'+STRING_VAF+'AgeMin" type="text" class="filter" size="2"></input>\
  150. &nbsp; \
  151. max: <input id="'+STRING_VAF+'AgeMax" type="text" class="filter" size="2"></input>\
  152. <br />\
  153. &nbsp; gender &nbsp;\
  154. <select id="'+STRING_VAF+'Gender" class="filter" name="gender" multiple="multiple" size="3">\
  155. <option value="" selected="selected">none specified</option>\
  156. '+options_gender+
  157. '</select>\
  158. \
  159. &nbsp; Role &nbsp;\
  160. <select id="'+STRING_VAF+'Role" class="filter" name="role" multiple="multiple" size="5">\
  161. <option value="" selected="selected">none specified</option>\
  162. '+options_role+
  163. '</select>'
  164. ;
  165. if(is_fetishes_page)
  166. {
  167. BLOCK += '\
  168. &nbsp; Into &nbsp;\
  169. <select id="'+STRING_VAF+'IntoStatus" multiple="multiple" size="3">\
  170. <option value="" selected="selected">All</option>\
  171. '+options_into_status+
  172. '</select>\
  173. <select id="'+STRING_VAF+'IntoActivity" multiple="multiple" size="3">\
  174. <option value="" selected="selected">All</option>\
  175. '+options_into_activity+
  176. '</select>\
  177. '
  178. ;
  179. }
  180. BLOCK += '<br />\
  181. <input type="hidden" name="'+STRING_VAF+'HasAvatar" ></input>\
  182. <input type="checkbox" id="'+STRING_VAF+'HasAvatar" name="'+STRING_VAF+'HasAvatar" checked="false"></input>\
  183. <label for="'+STRING_VAF+'HasAvatar">only with avatar</label>\
  184. <br />\
  185. <span id="'+STRING_VAF+'Buttons" style="display:inline;">\
  186. <input id="'+STRING_VAF+'ButtonGo" type="button" value="view&nbsp;all"></input>\
  187. <input id="'+STRING_VAF+'ButtonStop" type="button" value="&nbsp;stop&nbsp;"></input>\
  188. </span> <br />\
  189. <span id="'+STRING_VAF+'ShowCount">\
  190. </span> \
  191. </div> \
  192. </div>';
  193. if($(placeAfter).length > 0)
  194. $(placeAfter).after(BLOCK)
  195. else
  196. $('body').prepend(BLOCK);
  197. }
  198. function updateFilters()
  199. {
  200. $('#'+STRING_VAF+'Container').find('select, input[type=text]').each(function(){
  201.  
  202. var name = $(this).attr('id').replace(STRING_VAF,'')
  203. debug(name)
  204. if( typeof filters[name][2] != 'string')
  205. {
  206. var options = $(this).find('option:selected');
  207. filters[name][0] = false;
  208. filters[name][1] = [];
  209. $(options).each( function()
  210. {
  211. filters[name][0] = true;
  212. filters[name][1].push($(this).val());
  213. });
  214. if(filters[name][1].length == 1 && filters[name][1][0] == "" )
  215. filters[name][0] = false;
  216. }
  217. else
  218. if(filters[name][2] != $(this).val())
  219. {
  220. filters[name][0] = true;
  221. filters[name][1] = $(this).val();
  222. }
  223. else
  224. {
  225. filters[name][0] = false;
  226. filters[name][1] = filters[name][2];
  227. }
  228. });
  229. };
  230. /** due to recursive function*/
  231. function seekingEnded()
  232. {
  233. debug("total members: "+ members.length);
  234. showReFilter();
  235. showCount();
  236. }
  237. /*-----------------------------------*/
  238. function setContainers(pageUsers){
  239. var parent;
  240. $(pageUsers).each(function(){
  241. parent = $(this).parent().get(0);
  242. // debug("parent: "+$(parent).attr("class"))
  243.  
  244. if($.inArray(parent, listContainers) == -1)
  245. listContainers.push(parent);
  246. });
  247. }
  248. function setNext(anchor)
  249. {
  250. next = ( anchor.length > 0 )? 'https://fetlife.com' + anchor.attr('href'):'';
  251. }
  252. /*-----------------------------------*/
  253. function filter(n)
  254. {
  255. return (
  256. filter_role(n)
  257. && filter_name(n)
  258. && filter_location(n)
  259. && filter_has_avatar(n)
  260. && filter_gender(n)
  261. && filter_role(n)
  262. && filter_age_min(n)
  263. && filter_age_max(n)
  264. && filter_intoStatus(n)
  265. && filter_intoActivity(n)
  266. );
  267. }
  268. function filter_has_avatar(n){
  269. debug(' '+n+' '+getCache(n)[6])
  270. debug("val() "+$('#'+STRING_VAF+'HasAvatar:checked'))
  271.  
  272. if( $('#'+STRING_VAF+'HasAvatar:checked').length > 0 )
  273. {
  274. if( ! mCache[n][6]){
  275. debug('don\'t show')
  276. return false;
  277. }
  278. else
  279. debug('show')
  280. }
  281. else
  282. debug($('#'+STRING_VAF+'HasAvatar:checked').length > 0)
  283.  
  284. return true;
  285. }
  286. function filter_name(n)
  287. {
  288. if(filters['NameContains'][0])
  289. if( mCache[n][0].toLowerCase().indexOf(filters['NameContains'][1].toLowerCase()) < 0 )
  290. return false;
  291. return true;
  292. }
  293. /** TODO age */
  294. function filter_age_min(n)
  295. {
  296. if(filters['AgeMin'][0])
  297. if( mCache[n][1] <= parseInt(filters['AgeMin'][1]) )
  298. return false;
  299. return true;
  300. }
  301. function filter_age_max(n)
  302. {
  303. if(filters['AgeMax'][0])
  304. if( mCache[n][1] >= parseInt(filters['AgeMax'][1]) )
  305. return false;
  306. return true;
  307. }
  308. function filter_role(n)
  309. {
  310. if(filters['Role'][0])
  311. if($.inArray(mCache[n][3], filters['Role'][1]) < 0)
  312. return false;
  313. return true;
  314. }
  315. function filter_gender(n)
  316. {
  317. if(filters['Gender'][0])
  318. if($.inArray(mCache[n][2], filters['Gender'][1]) < 0)
  319. return false;
  320. return true;
  321. }
  322. function filter_location(n)
  323. {
  324. if(filters['LocContains'][0])
  325. if( mCache[n][4].toLowerCase().indexOf(filters['LocContains'][1].toLowerCase()) < 0 )
  326. return false;
  327. return true;
  328. }
  329. function filter_intoStatus(n)
  330. {
  331. if(filters['IntoStatus'][0])
  332. if($.inArray(mCache[n][7], filters['IntoStatus'][1]) < 0)
  333. return false;
  334. return true;
  335. }
  336. function filter_intoActivity(n)
  337. {
  338. if(filters['IntoActivity'][0])
  339. if($.inArray(mCache[n][8], filters['IntoActivity'][1]) < 0)
  340. return false;
  341. return true;
  342. }
  343. /*-----------------------------------*/
  344. function lookPage()
  345. {
  346. // var currentUsers = $(STRING_USERS);
  347. // var index = 0
  348. // currentUsers.each(function()
  349. // {
  350. // index = storeUser(this);
  351. // $(this).remove();
  352. // show(index);
  353. // });
  354. }
  355. function cleanPage(){
  356. $(STRING_USERS).remove();
  357. $("div.pagination").remove();
  358. }
  359. function lookAhead (firstPage)
  360. {
  361. if( firstPage )
  362. next = location.href + ( location.search.toString().length > 0 ? "&page=1" : "?page=1" );
  363. if( next != '' || ! next.match(/\s*/) )
  364. {
  365. debug("next: "+next)
  366. if(! ajaxLocked){
  367. ajaxLocked = true;
  368. $.ajax({
  369. url: next,
  370. dataType: 'html',
  371. useCache: false,
  372. success: function(data)
  373. {
  374. setNext($(data).find('a.next_page'));
  375. $(data).find(STRING_USERS).each(function(){show(storeUser(this))});
  376. ajaxLocked = false;
  377. lookAhead(false);
  378. }
  379. });
  380. }
  381. showCount()
  382. }
  383. else
  384. {
  385. /*seeking END (current and other pages), now all users are stored in members array*/
  386. seekingEnded()
  387. }
  388. }
  389. /*-----------------------------------*/
  390.  
  391. function showCount(){
  392. $('#'+STRING_VAF+'ShowCount').html("members: "+currentCount+" of "+members.length)
  393. }
  394. function show(n)
  395. {
  396. if(filter(n))
  397. {
  398. $(listContainers[altPos]).append(members[n]);
  399. currentCount++;
  400. altPos = ( altPos == (listContainers.length -1) )? 0 : (altPos+1);
  401. }
  402. }
  403. /*-----------------------------------*/
  404. function storeUser(user){
  405. var i = (members.push(user) - 1);
  406. var M = []; /* match: [whole, age (not null), gender, role ] */
  407. var firstSpan = $(user).find('div:eq(1) span:first');
  408. var C, into, regInto;
  409. var avatar = $(user).find('img.profile_avatar');
  410. mCache[i] = ['', 0, '', '', '', '', false, '', ''];
  411. C = mCache[i];
  412. /* name */
  413. C[0] = firstSpan.text();
  414. /** profile url */
  415. C[5] = firstSpan.find('a:first').attr('href');
  416.  
  417. /** hasAvatar, need to be false if user has */
  418. C[6] = ( avatar.attr('src').indexOf('/images/avatar_missing') < 0 );
  419.  
  420. M = $(user).find('div span:nth-child(2)').text().match(userRegExp);
  421. // debug("match: "+(M[1]||"")+", "+(M[2]||"")+", "+(M[3]||""));
  422. /* age */
  423. C[1] = M[1];
  424. /* gender */
  425. C[2] = M[2];
  426. /* role */
  427. C[3] = M[3];
  428. /* location*/
  429. C[4] = $(user).find('div:eq(1) em').text()||'';
  430. /* into */
  431. if(is_fetishes_page)
  432. {
  433. M = []; /* match: ["into status", "rest aka into activity" ] */
  434. into = $(user).find('div:eq(1) span:eq(2)').text()||'';
  435. regInto = new RegExp('^('+ARRAY_INTO_STATUS.join('|')+') ?(.*$)?');
  436. M = into.match(regInto);
  437. if(M)
  438. {
  439. C[7] = M[1]||'';
  440. C[8] = M[2]||'';
  441. }
  442. // debug("mCache[i][7] = "+C[7]+" && mCache[i][8] = "+C[8])
  443. }
  444. debug(C);
  445. return i;
  446. }
  447. function getCache(n){
  448. return gC(n);
  449. }
  450. function gC(n){
  451. return mCache[n];
  452. }
  453. /*-----------------------------------*/
  454. function showReFilter()
  455. {
  456. $('#'+STRING_VAF+'Buttons').append(
  457. '<input id="'+STRING_VAF+'ButtonReFilter" type="button" value="filter&nbsp;again"></input>'
  458. )
  459. $('#'+STRING_VAF+'ButtonGo').remove();
  460. $('#'+STRING_VAF+'ButtonReFilter').click(function()
  461. {
  462. altPos = 0, currentCount = 0;
  463. window.scrollTo(0,0);
  464. updateFilters();
  465. debug(filters);
  466. $(STRING_USERS).remove();
  467. for(var i = 0; i < members.length; i++){
  468. show(i)
  469. }
  470. showCount();
  471. });
  472. }
  473. var count=0;
  474.  
  475. if(typeof $ == 'function')
  476. init();
  477. else
  478. { setTimeout( 5000, function(){ if(typeof $ != 'function') alert('fetlife is modified, script '+ GM_info.script.name +'can\'t run please contact the author OR visit http://'+GM_info.script.namespace+'.')});}
  479. /*-----------------------------------*/
  480. })(jQuery)
  481.  
  482. jQuery.noConflict(true);