fetlife_all_members

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

As of 04/07/2014. See the latest version.

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