// ==UserScript==
// @name fetlife_all_members (ASL+role+status filter)
// @namespace io.github.bewam
// @description greasemonkey script to filter fetlife members when it's possible. Search by name, gender, role, age, location or status.
// @include https://fetlife.com/*
// @version 1.9.2.20180609
// @run-at document-end
// @include-jquery false
// @use-greasemonkey true
// @require http://code.jquery.com/jquery-2.1.1.min.js
// ==/UserScript==
// NOTE: comment (change first "/**/" to "/*") for debugging.
console = { log: ()=>{}}; /**/
var useCurrentPageAsDefault = false;
var onlyWithAvatar = false; //actual default see #4 on github
/* care modifing
* TODO : to be removed: https://greasyfork.org/fr/forum/discussion/4199/lock-a-script#latest
*/
const FETCH_LATENCY = 2000;
// jshint ignore: start
const forceNoAvatar = true;// see #4 on github du to fetlife restriction + ajax
+(function ($) {
// jshint ignore: end
// jquery str
const Selector = {
currentPage: 'em.current',
body: 'body',
users: 'div.fl-member-card',
pagination: "div.pagination",
nextPage: 'a.next_page',
nextDisabled: 'span.next_page.disabled',
previousPage: 'a.previous_page',
firstUser: '.fl-member-card:first',
user: {
body: '.fl-flag__body',
imageAvatar: 'img.fl-avatar__img',
firstSpan: '.fl-member-card__user',
profileLink: 'a.fl-member-card__user',
shortDesc: '.fl-member-card__info',
/** age, gender, role */
location: 'span.fl-member-card__location',
into: 'span.fl-member-card__action span',
}
};
// script_name avoid name/id conflicts
const _STR = 'fetlife_all_members';
const ARRAY_GENDER = [
'M',
'F',
'NB',
'GQ',
'GF',
'TG',
'MtF',
'FtM',
'CD/TV',
'IS',
'FEM',
'B',
'N/A'
];
const ARRAY_GENDER_LABEL = [
'Male',
'Female',
'Non-Binary',
'Genderqueer',
'Gender Fluid',
'Transgender',
'Trans - Male to Female',
'Trans - Female to Male',
'Crossdresser/Transvestite',
'Intersex',
'Femme',
'Butch',
'Not Applicable'
];
const ARRAY_ROLE = [
'Dominant',
'Domme',
'Switch',
'submissive',
'Master',
'Mistress',
'slave',
'kajira',
'kajirus',
'Top',
'Bottom',
'Sadist',
'Masochist',
'Sadomasochist',
'Disciplinarian',
'Kinkster',
'Fetishist',
'Swinger',
'Hedonist',
'Sensualist',
'Exhibitionist',
'Voyeur',
'Daddy',
'Mommy',
'babygirl',
'babyboy',
'Ageplayer',
'Little',
'Middle',
'Big',
'Brat',
'Princess',
'Slut',
'Doll',
'Toy',
'Cougar',
'Bull',
'Hotwife',
'Cuckoldress',
'cuckold',
'cuckquean',
'sissy',
'Furry',
'pet',
'kitten',
'pup',
'pony',
'Handler',
'Primal',
'Primal Predator',
'Primal Prey',
'Spanko',
'Spanker',
'Spankee',
'Rigger',
'Rope Top',
'Rope Bottom',
'Rope Bunny',
'Leatherman',
'Leatherwoman',
'Leather Daddy',
'Leather Mommy',
'Leather Top',
'Leather bottom',
'Leatherboy',
'Leathergirl',
'Leatherboi',
'Bootblack',
'Drag King',
'Drag Queen',
'Evolving',
'Exploring',
'Vanilla',
'Undecided',
'Not Applicable'
];
const ARRAY_ROLE_LABEL = ARRAY_ROLE;
const ARRAY_INTO_STATUS = [
'is into',
'is curious about'
];
const ARRAY_INTO_ACTIVITY = [
'giving',
'receiving',
'watching',
'wearing',
'watching others wear',
'everything to do with it'
];
const marker = {
folded: '>',
unfolded: 'v'
};
const isFetishesPage = /^\/fetishes/.test(location.pathname);
const imageMissingData =
`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`;
// NOTE: do not modify unless you know what you're doing.
var overlay = (form) =>
$(Selector.firstUser)
.parents('.clearfix:first')
.prepend(form)
.length;
/** TODO @class to manage pages */
// Pagination = {
// _next: '',
// _previous: '',
// _currentNo: '',
// _clientPageNo,
// setNext: setNext,
// getNext: getNext,
// getPrevious :() => this._previous,
// setPrevious : (str) => {this._previous = str;},
// getCurrentNo: () => this._currentNo ,
// setCurrentNo: (n) => {this._currentNo = n;},
// };
// var page = Object.create(Pagination);
var lastPageNumber,
_pageCurrentNo,
_clientPageNo,
InputcurrentPageDefaultVal,
_fromPage = -1,
_toPage = -1,
pageMin = 1,
pageMax = 1,
_pageNext = '';
/* balance columns */
var alternColumn = 0,
_shownCount = 0;
/* store user html block index is shared with mCache */
/** NOTE see initUserCache() for desc. */
var mCache = [],
/** referrer (pages) */
rCache = [],
/** image avatar data */
aCache = [],
listContainers = [] // columns where lists appear
;
//TODO Expressions =
var userRegExp = new RegExp('([0-9]{2})(' + ARRAY_GENDER.join('|') +
')? (' +
ARRAY_ROLE.join('|') + ')?', 'i');
var regInto = new RegExp('^(' + ARRAY_INTO_STATUS.join('|') +
') ?(.*$)?');
/** modified, value, default value */
var filters = {
'NameContains': [false, '', ''],
'AgeMin': [false, '', ''],
'AgeMax': [false, '', ''],
'Gender': [false, [], []],
'Role': [false, [], []],
'LocContains': [false, '', ''],
'IntoStatus': [false, '', ''],
'IntoActivity': [false, '', '']
// has_avatar: @see function
};
var ajaxLocked = false,
scriptLaunched = false,
stopped = false;
/* jshint ignore:start */
addStyle(
'#' + S('Wrapper') + ' { ' +
// 'background-color: rgba(255, 255, 255, 0.4);' +
'border: solid 2px lightgray;' +
'color: white !important;/**/ ' +
'padding:5px;' +
'min-height:15px !important;' +
'vertical-align:middle;' +
'margin-bottom: 10px;' +
'} ' +
'#' + S('Controls') + ' { ' +
'display: block;/**/ ' +
'min-height:15px !important;' +
'}' +
'#' + S('Content') + ' { ' +
'display: none;/**/ ' +
'margin-top: 5px;' +
'}' +
'#' + S('Buttons') + '{ ' +
'margin-top: 10px;' +
'}' +
'#' + S('ButtonStop') + '{ ' +
// 'display: none;' +
'}' +
`.flfm-has-avatar{
border: solid 1px red;
}`
);
/* jshint ignore:end */
/*-----------------------------------*/
/*----------------html related--------------------------*/
function buildOptions(arr1, arr2) {
var str = '';
$.each(arr1, function (i, v) {
str += '<option value="' + v + '" >' + arr2[i] +
' </option>';
});
return str;
}
function addStyle(css) {
var newStyleSheet = document.createElement('style');
document.body.appendChild(newStyleSheet);
newStyleSheet.textContent = css;
return newStyleSheet;
}
function drawBlock() {
var optionsGender = buildOptions(ARRAY_GENDER, ARRAY_GENDER_LABEL);
var optionsRole = buildOptions(ARRAY_ROLE, ARRAY_ROLE_LABEL);
var optionsIntoStatus = buildOptions(ARRAY_INTO_STATUS,
ARRAY_INTO_STATUS);
var optionsIntoActivity = buildOptions(ARRAY_INTO_ACTIVITY,
ARRAY_INTO_ACTIVITY);
var BLOCK =
'<div id="' + S('Wrapper') + '"> ' +
' <div id="' + S('Controls') + '">' +
' <b>></b> ' +
' <u>filter members</u>' +
' </div>' +
' <div id="' + S('Content') + '"> ' +
' <div id="' + S('Filters') + '">' +
' <label for="' + S('NameContains') +
'">name: </label>' +
' <input id="' + S('NameContains') +
'" type="text" class="filter"></input>' +
' <br />' +
' <label for="' + S('LocContains') +
'">location:</label>' +
' <input id="' + S('LocContains') +
'" type="text" class="filter" ></input>' +
' <br />' +
' <u>age</u> ' +
' <label for="' + S('AgeMin') + '">min:</label>' +
' <input id="' + S('AgeMin') +
'" type="text" class="filter" size="2"></input>' +
' ' +
' <label for="' + S('AgeMax') + '">max:</label>' +
' <input id="' + S('AgeMax') +
'" type="text" class="filter" size="2"></input>' +
' <br />' +
' <small>' +
' Use CTRL key to multiple select.' +
' <br />' +
' Use MAJ key to do a range select.' +
' </small>' +
' <br />' +
' <label for="' + S('Gender') + '">gender:</label>' +
' <select id="' + S('Gender') +
'" class="filter" name="gender" multiple="multiple" size="3">' +
' <option value="" selected="selected">none specified</option>' +
optionsGender +
' </select>' +
' <label for="' + S('Role') + '">role:</label>' +
' <select id="' + S('Role') +
'" class="filter" name="role" multiple="multiple" size="5">' +
' <option value="" selected="selected">none specified</option>' +
optionsRole +
' </select>';
if(isFetishesPage) {
BLOCK +=
' Into ' +
' <select id="' + S('IntoStatus') +
'" multiple="multiple" size="3">' +
' <option value="" selected="selected">All</option>' +
optionsIntoStatus +
' </select>' +
' <select id="' + S('IntoActivity') +
'" multiple="multiple" size="3">' +
' <option value="" selected="selected">All</option>' +
optionsIntoActivity +
' </select>';
}
BLOCK +=
' <br />' +
// ' <input type="hidden" name="' + S('HasAvatar') +
// '" ></input>' +
' <input type="checkbox" id="' + S('HasAvatar') +
'" name="' + S(
'HasAvatar') + '" ' + (onlyWithAvatar ? 'checked="true"' :
'false') + '></input>' +
' <label for="' + S('HasAvatar') +
'">only with avatar</label>' +
' <br />' +
' <label for="' + S('FromPage') +
'">From page: </label>' +
' <input id="' + S('FromPage') +
'" type="text" class="filter" size="3" value="' +
InputcurrentPageDefaultVal + '"></input>' +
' <input id="' + S('ButtonCurrentPage') +
'" type="button" value="current"></input>' +
' <label for="' + S('ToPage') +
'"> to page: </label>' +
' <input id="' + S('ToPage') + '" name="' + S('ToPage') +
'" type="text" class="filter" size="3"></input>' +
' <br />' +
' <br />' +
' </div> ' + // Filters
' <div id="' + S('Buttons') + '" style="display:inline;">' +
' <input id="' + S('ButtonGo') +
'" type="button" value="view all"></input>' +
' <input id="' + S('ButtonStop') +
'" type="button" value=" stop " disabled="true"></input>' +
' </div>' + // Buttons
' <div id="' + S('Info') + '">' +
' <br />' +
' <span id="' + S('ShowCount') + '">' +
' </span> ' +
' </div> ' + // Info
' </div> ' + //Content
'</div>'; // Wrapper
if(overlay(BLOCK) < 1) {
$(Selector.body)
.prepend(BLOCK);
}
}
function disableAllInput() {
// TODO @class Form. Form.disableEntries() => void()
$I('Content')
.find('*')
.attr("disabled", 'true');
$I('ButtonStop')
.removeAttr("disabled");
}
function enableAllInput() {
$I('Content')
.find('*')
.removeAttr("disabled");
$I('ButtonStop')
.attr("disabled", 'true');
}
function disableInput() {
// TODO @function Form.disableEntries() => void()
$I('Content')
.find('*')
.attr("disabled", 'true');
}
function enableInput() {
$I('Content')
.find('*')
.removeAttr("disabled");
}
/**
* @param str id
* @return object jQuery with the tagged (script name) id
*/
function $I(id) {
var str = arguments.length > 1 ? arguments[1] : '';
return $('#' + S(id) + str);
}
function S(name) {
return _STR + name;
}
function rS(str) {
return str.replace(_STR, '');
}
function cleanPage() {
$(Selector.users)
.remove();
$(Selector.pagination)
.hide();
_shownCount = 0;
}
function toggleMarker($el) {
var cnt = $el.html();
$el.html(cnt == marker.unfolded ? marker.folded : marker.unfolded);
}
/*-----------------------------------*/
/** Would grab 2 containers where to put members in */
function initContainers(pageUsers) {
var parent;
$(pageUsers)
.each(function () {
parent = $(this)
.parent()
.get(0);
console.log("container: " + $(parent)
.attr("class"));
if($.inArray(parent, listContainers) == -1) {
listContainers.push(parent);
}
});
}
/*------------------------------------*/
function updateMemberFilters() {
$I('Content')
.find('select, input[type=text]')
.each(function () {
var name = rS($(this)
.attr('id'));
console.log('updating filter: ' + name);
if(!filters[name]) {
return;
}
if(typeof filters[name][2] != 'string') {
var options = $(this)
.find('option:selected');
filters[name][0] = false;
filters[name][1] = [];
$(options)
.each(function () {
filters[name][0] = true;
filters[name][1].push($(this)
.val());
});
if(filters[name][1].length == 1 && filters[name][1]
[0] === '') {
filters[name][0] = false;
}
} else
if(filters[name][2] != $(this)
.val()) {
filters[name][0] = true;
filters[name][1] = $(this)
.val();
} else {
filters[name][0] = false;
filters[name][1] = filters[name][2];
}
});
console.log(filters);
}
function updateAvatarFilter() {
onlyWithAvatar = ($I('HasAvatar', ':checked')
.length > 0);
}
function updatePaginationFilters() {
var reload = false;
var $fromPage = parseInt($I('FromPage')
.val());
var $toPage = parseInt($I('ToPage')
.val());
if(_fromPage != $fromPage) {
_fromPage = $fromPage;
reload = true;
}
if(_toPage != $toPage) {
_toPage = $toPage;
reload = true;
}
console.log(' _fromPage ' + _fromPage + ' $fromPage ' + $fromPage);
console.log(' _toPage ' + _toPage + ' $toPage ' + $toPage);
return reload;
}
/*-----------------------------------*/
var Refining = function (cacheItem) {
var c = cacheItem;
var filtered = false;
/**
* @return bool false by default, element is not trapped */
this.isFiltered = function () {
// NOTE for debug purpose uncomment the second block and / comment this one.
// TODO how secure is eval() ?
/**
role();
name();
location();
hasAvatar();
gender();
ageMin();
ageMax();
intoStatus();
intoActivity();
/**/
/**/
var functions = [
"ageMax",
"ageMin",
"gender",
"hasAvatar",
"intoActivity",
"intoStatus",
"location",
"name",
"role"
];
for(var fn of functions) {
// jshint ignore : start
eval(fn + '()');
// jshint ignore : end
if(getF()) {
console.log('is filtered: ' + fn.toString());
console.log(c);
// stop filtering here
return true;
}
}
/**/
return filtered;
};
/**
* @arg boolean filtered ?
* @return void(0)
*/
function setF(F) {
if(F) {
filtered = true;
}
}
function getF() {
return filtered;
}
/** NOTE Each filter (except hasAvatar) check if filter was modified
(updateMemberFilters) and look if the changes concern the current
user. Filter must set false if ok.
*/
function ageMax() {
if(filters.AgeMax[0]) {
setF(c[1] > parseInt(filters.AgeMax[1]));
}
}
function ageMin() {
if(filters.AgeMin[0]) {
setF(c[1] < parseInt(filters.AgeMin[1]));
}
}
function gender() {
if(filters.Gender[0]) {
setF($.inArray(c[2], filters.Gender[1]) < 0);
}
}
function hasAvatar() {
// console.log('avatar ' + onlyWithAvatar.toString());
setF((onlyWithAvatar && !c[6]) ? true : false);
}
function intoActivity() {
if(filters.IntoActivity[0]) {
setF($.inArray(c[8], filters.IntoActivity[1]) < 0);
}
}
function intoStatus() {
if(filters.IntoStatus[0]) {
setF($.inArray(c[7], filters.IntoStatus[1]) < 0);
}
}
function location() {
if(filters.LocContains[0]) {
setF(
c[4].toLowerCase()
.indexOf(
filters.LocContains[1].toLowerCase()
) < 0
);
}
}
function name() {
if(filters.NameContains[0]) {
setF(
c[0].toLowerCase()
.indexOf(
filters.NameContains[1].toLowerCase()
) < 0
);
}
}
function role() {
if(filters.Role[0]) {
setF($.inArray(c[3], filters.Role[1]) < 0);
}
}
}; // Filter
/** filter each user */
function filterUser(n) {
// console.log('filtering n°' + n);
var o;
var filter = new Refining(mCache[n]);
return(filter.isFiltered());
}
/*-----------Ajax & pagination -----------------*/
/** @param mixed (str or jQ.), a link "next" in pagination */
function setNext(mix) {
console.log('setNext: ');
console.log(mix);
var next = '';
if(mix.nodeName === 'A') {
next = mix.href;
} else if(typeof mix == 'string') {
next = mix;
} else if(
(typeof mix == 'function' ||
typeof mix == 'object') &&
$(mix)
.is('A')
) {
next = mix.attr('href');
}
_pageNext = next;
return(next);
}
function getNext() {
console.log(_pageNext);
if(_pageNext === '' || _pageNext.match(/^\s*$/)) {
return '';
}
if(_pageNext.indexOf("fetlife.com") < 0) {
return 'https://fetlife.com/' + _pageNext;
} else {
return _pageNext;
}
}
function getCurrentPageNo() {
return parseInt($(Selector.currentPage)
.text());
}
function getLastPageNum() {
var $e = $(Selector.nextPage);
if($e.length > 0) {
return parseInt($e.prev()
.text());
}
$e = $(Selector.nextDisabled);
if($e.length > 0) {
return parseInt($e.prev()
.text());
}
return -1;
}
function isLastFetchedPage(next) {
var p = next.lastIndexOf('?');
var search;
var S;
if(p > -1) {
// url.search W/o leading ?
search = next.substr(p + 1);
} else {
// no url.search, stop now
// TODO throw warning
return true;
}
S = search.split('&');
for(var i = 0; i < S.length; i++) {
if(S[i].substr(0, 5) === 'page=') {
return(parseInt(S[i].substr(5)) >= parseInt(_toPage) + 1);
}
}
// unknown case, stop now
// TODO throw warning
return true;
}
function addUrlString(url, pageNb) {
var S = location.search.toString();
console.log('location.search: ' + S);
if(S.length <= 0) {
return url + "?page=" + pageNb;
}
if(S.indexOf('page=') > -1) {
return url.replace(/page=\d+/, 'page=' + pageNb);
} else
return url + '&page=' + pageNb;
// FIXME: return what ? return (url + '&page=' + pageNb);
}
function fetchMembers(startPageNo) {
// if (typeof startPageNo === 'undefined')
// seekingEnded();
var next;
if(startPageNo) {
console.log('start fetching from Page: ' + startPageNo);
if(parseInt(startPageNo) > 0) {
setNext(addUrlString(location.href, startPageNo));
}
} else {
startPageNo = false;
}
next = getNext();
console.log("trying to fetch: " + next);
if(
next === '' ||
next.match(/^\s*$/) ||
next === void(0)
) {
seekingEnded();
return;
}
if(!ajaxLocked) {
ajaxLocked = true;
$.ajax({
url: next,
dataType: 'html',
useCache: false,
success: (data) => {
var pageIndex = storePage(next)
onMembersPage(data, pageIndex);
},
error: function (event) {
seekingEnded();
console.error(event);
}
});
}
showCount();
}
function onMembersPage(data, pIndex) {
var aNext = $(data)
.find(Selector.nextPage);
setNext(aNext);
console.log("next page to fetch: " +
getNext());
var users = $(data)
.find(Selector.users);
$.each(users, (i, user) => {
var cacheIndex = initUserCache();
mCache[cacheIndex][9] = pIndex;
show(storeUser(cacheIndex, user));
});
if(!stopped && !isLastFetchedPage(getNext())) {
setTimeout(
function () {
ajaxLocked = false;
fetchMembers(false);
}, (FETCH_LATENCY < 0 ? FETCH_LATENCY : 0)
);
} else {
seekingEnded();
}
}
function storePage(url) {
return(rCache.push(url) - 1);
}
function storeAvatar(src, userIndex) {
var C = mCache[userIndex];
var referrer = rCache[C[9]];
if(! C[6]) {
console.log('user "', C[0], '" has no avatar');
aCache[userIndex] = false;
return;
}
$.ajax({
url: src,
type: "GET",
headers: {
"X-Alt-Referer": referrer
},
crossDomain: true,
xhrFields: {
withCredentials: true,
// responseType: 'blob'
},
success: function (data) {
aCache[userIndex] = btoa(data);
imgData = getAvatarData(userIndex);
$('body')
.prepend('img')
.attr('src', imgData)
},
error: function (event) {
seekingEnded();
console.error(event);
}
})
.done(function () {
console.log("img success");
})
.fail(function () {
console.log("img error");
})
.always(function () {
console.log("img complete");
});
}
function showInfo(str) {
$I('ShowCount')
.html(str);
}
function showCount() {
showInfo("members: " + _shownCount + " of " + mCache.length);
}
function show(n) {
var html;
if(!filterUser(n)) {
html = drawMemberCard(n)
$(listContainers[alternColumn])
.append(html)
// console.log(html);
_shownCount++;
alternColumn = (alternColumn == (listContainers.length - 1)) ?
0 : (
alternColumn + 1);
}
}
function drawMemberCard(userIndex) {
/* 0:name, 1:age, 2:gender, 3:role, 4:location, 5:profileURL, 6:hasAvatar, 7:status, 8:activity */
var C = mCache[userIndex];
var avatar = getAvatarData(userIndex);
var name = C[0];
var age = C[1];
var gender = C[2];
var role = C[3];
var location = C[4];
var profileURL = C[5];
var hasAvatarClass = C[6] ? 'flfm-has-avatar':'';
var status = C[7];
var activity = C[8];
var memberCardHTML =
`<div class="fl-member-card fl-flag">
<div class="fl-flag__image ${hasAvatarClass}">
<a class="fl-avatar__link" href="${profileURL}">
<img alt="${name}" title="${name}" class="fl-avatar__img" src="${avatar}" width="73" height="73"></a>
</div>
<div class="fl-flag__body">
<a class="fl-member-card__user" href="${profileURL}">${name}</a>
<span class="fl-member-card__info">
${age}${gender}
${role}
</span>
<span class="fl-member-card__location">
${location}
</span>`;
memberCardHTML += isFetishesPage ?
`
<div class="fl-member-card__action">
<span class="quiet small">
${status} ${activity}
</span>
</div>` :
'';
memberCardHTML += `</div>
</div>`;
return memberCardHTML;
}
// TODO for 2.0
// var Cache = function () {}
//
// var Member = function () {
//
// htmlCache = [];
//
//
// var isfilter = function (n) { Refining.isFiltered()}
// var store = function () {};
//
// this.add = function () {};
// this.show = function () {};
// this.get = function (n) {};
//
// return this;
// };
/**
0:name, 1:age, 2:gender, 3:role, 4:location, 5:profileURL, 6:hasAvatar, 7:status, 8:activity, 9(internal): pageCacheIndex
**/
function initUserCache() {
var defaults = ['', 0, '', '', '', '', false, '', '', -1];
var cacheLen = mCache.push(defaults);
var userIndex = cacheLen - 1;
return userIndex;
}
function getAvatarData(userIndex) {
var data = imageMissingData;
if(mCache[userIndex][6]) {
data = aCache[userIndex];
}
return "data:image/jpg;base64," + data;
}
function storeUser(cacheIndex, userData) {
var matches = [];
var firstSpan = $(userData)
.find(Selector.user.firstSpan);
var C, into, src;
var $avatar = $(userData)
.find(Selector.user.imageAvatar);
C = mCache[cacheIndex];
/* name */
C[0] = firstSpan.text();
/** profile url */
C[5] = $(userData)
.find(Selector.user.profileLink)
.attr('href');
src = $avatar.attr('src')
console.log(src);
/** hasAvatar, need to be false if user has */
if(src.indexOf('/images/avatar_missing') < 0 ) {
C[6] = true;
if (! forceNoAvatar ) {
storeAvatar(src, cacheIndex);
}
};
try {
var shortDesc = $(userData)
.find(Selector.user.shortDesc)
.text()
.replace(/\n|\r/g, ' ')
.replace(/\s{1,}|\n|\r/g, ' ');
console.log(shortDesc)
var matches = shortDesc.match(userRegExp);
if(matches.length < 2) {
return;
}
/* age */
C[1] = matches[1] || '';
/* gender */
C[2] = matches[2] || '';
/* role */
C[3] = matches[3] || '';
/* location*/
C[4] = $(userData)
.find(Selector.user.location)
.text() || '';
/* into */
if(isFetishesPage) {
matches = []; /* match: ["into status", "rest aka into activity" ] */
into = $(userData)
.find(Selector.user.into)
.text() || '';
// console.log('into: ' + into);
matches = into.match(regInto);
// console.log(matches);
if(matches) {
C[7] = matches[1] || '';
C[8] = matches[2] || '';
}
// console.log("mCache[i][7] = "+C[7]+" && mCache[i][8] = "+C[8])
}
} catch(err) {
console.log(err.message);
throw(new Error(err.message));
}
console.log(C);
return cacheIndex;
}
function initCaches() {
// referrer data to get avatars
rCache = [];
// avatars DATA
aCache = [];
// members
mCache = [];
}
function clearCache() {
// referrer data to get avatars
rCache = [];
// avatars DATA
aCache = [];
// members
mCache = [];
}
function gC(n) {
return mCache[n];
}
function getCache(n) {
return gC(n);
}
/*--------------helpers---------------*/
function isInt(n) {
return(!isNaN(n));
}
/*--------------Actions--------------*/
function init() {
var $pageNext = $(Selector.nextPage);
var $previousPage = $(Selector.previousPage);
var $pageUsers = $(Selector.users);
var $fromPage = $I('FromPage');
/** next_page link and list of members are on current page ? go on */
if(($pageNext.length > 0 || $previousPage.length > 0) &&
$pageUsers.length > 0
) {
_clientPageNo = _pageCurrentNo = getCurrentPageNo();
initContainers($pageUsers);
console.log('_pageCurrentNo :' + _pageCurrentNo);
drawBlock();
$I('FromPage')
.val(
useCurrentPageAsDefault ?
_pageCurrentNo :
1
);
pageMax = lastPageNumber = getLastPageNum();
console.log("lastPageNumber: " + lastPageNumber);
$I('ToPage')
.val(lastPageNumber);
setNext($pageNext);
initListener();
}
}
function showFilterAgain() {
$I('ButtonGo')
.val("filter again");
enableAllInput();
$I('ButtonGo')
.bind('click', () => {
if(updatePaginationFilters()) {
launchAgain();
$(this)
.unbind('click');
} else {
filterAgain();
}
});
}
function initListener() {
$I('Controls')
.click(function () {
$I('Content')
.toggle("slow");
toggleMarker($(this)
.find('b:first'));
});
$I('ButtonCurrentPage')
.click(function () {
$I('FromPage')
.val(_pageCurrentNo);
});
$I('ButtonGo')
.click(function () {
$(this)
.unbind('click');
launchSearch();
});
$I('ButtonStop')
.click(function () {
// $(this).unbind('click');
stopped = true;
});
}
function launchSearch() {
if(!scriptLaunched) {
updatePaginationFilters();
console.log("_fromPage: " + _fromPage);
console.log("_toPage: " + _toPage);
showInfo("loading ...");
updateMemberFilters();
updateAvatarFilter();
cleanPage();
disableAllInput();
fetchMembers(_fromPage);
scriptLaunched = true;
}
}
function launchAgain() {
cleanPage();
clearCache();
scriptLaunched = false;
launchSearch();
}
function filterAgain() {
alternColumn = 0;
// window.scrollTo(0, 0);
updateMemberFilters();
updateAvatarFilter();
cleanPage();
for(var i = 0; i < mCache.length; i++) {
show(i);
}
showCount();
}
/** due to recursive function*/
function seekingEnded() {
console.log("total members: " + mCache.length);
ajaxLocked = false;
stopped = false;
enableAllInput();
showFilterAgain();
showCount();
}
/* jshint ignore:start */
var count = 0;
if(typeof $ == 'function') {
init();
} else {
setTimeout(function () {
if(typeof $ !== 'function') {
alert('fetlife is modified, script ' +
GM_info.script.name +
'can\'t run please contact the author.');
}
}, 3000);
}
/*-----------------------------------*/
})(jQuery);
jQuery.noConflict(true);
/* jshint ignore:end */