Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save SokolskyNikita/9413127e483a0f0486faa46bdb6af7ce to your computer and use it in GitHub Desktop.

Select an option

Save SokolskyNikita/9413127e483a0f0486faa46bdb6af7ce to your computer and use it in GitHub Desktop.
Photofeeler: auto-select test type, ages, and credit count
// ==UserScript==
// @name Photofeeler Auto-Select
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Auto-fills and auto-advances through Photofeeler test setup
// @match https://www.photofeeler.com/*
// @grant none
// ==/UserScript==
'use strict';
/* ══════════════════════════════════════════════════════════════════════════
* CONFIGURATION
*
* category : 'dating' | 'business' | 'social'
*
* subject.gender : 'MALE' | 'FEMALE'
* subject.age : 18–99
*
* voters.gender : 'MALE' | 'FEMALE' | 'BOTH'
* voters.ageSliderMin: lower handle (0 = no lower bound, leave at 0)
* voters.ageSliderMax: upper handle step → displayed age cap:
* 1 → Up to ~24 2 → Up to ~29 3 → Up to ~34
* 4 → Up to ~44 5 → Any age
*
* testSize : 0 (Karma/free) | 10 | 20 | 40 | 80 credits
*
* autoNext : true = auto-click Next after subject & voter screens
* ══════════════════════════════════════════════════════════════════════════ */
const CFG = {
category: 'dating',
subject: { gender: 'MALE', age: 32 },
voters: { gender: 'FEMALE', ageSliderMin: 0, ageSliderMax: 2 },
testSize: 20,
autoNext: true,
};
/* ══════════════════════════════════════════════════════════════════════════ */
const CREDIT_LABEL = {
0: 'Karma Test',
10: 'Rough Test',
20: 'Standard Test',
40: 'Precise Test',
80: 'Very Precise Test',
};
// Per-run state — reset whenever the category panel reappears (new test flow)
let done = { category: false, subject: false, target: false, testSize: false };
function isVisible(el) {
if (!el) return false;
const r = el.getBoundingClientRect();
return r.width > 0 && r.height > 0;
}
function fire(el, ...types) {
types.forEach(t => el.dispatchEvent(new Event(t, { bubbles: true })));
}
// ── Step 1: Click the correct category ──────────────────────────────────────
function handleCategory() {
const panel = document.querySelector('.panel-new-category-25b');
if (!panel || !isVisible(panel)) return false;
// If category panel is visible and we haven't acted yet, reset full state
// (handles navigating back to start a new test)
if (!done.category) {
done = { category: false, subject: false, target: false, testSize: false };
} else {
return false; // already clicked
}
const target = panel.querySelector(`.category.${CFG.category}`);
if (!target) { console.warn('[PF] category not found:', CFG.category); return false; }
target.click();
done.category = true;
console.log('[PF] clicked category:', CFG.category);
return true;
}
// ── Step 2: Fill subject (gender + age) and optionally click Next ────────────
function handleSubject() {
if (!done.category || done.subject) return false;
const form = [...document.querySelectorAll('form.panel-subject')]
.find(f => isVisible(f));
if (!form) return false;
const [genderSel, ageSel] = form.querySelectorAll('select.native');
if (!genderSel || !ageSel) return false;
let changed = false;
if (genderSel.value !== CFG.subject.gender) {
genderSel.value = CFG.subject.gender;
fire(genderSel, 'input', 'change');
changed = true;
}
const ageStr = String(CFG.subject.age);
if (ageSel.value !== ageStr) {
ageSel.value = ageStr;
fire(ageSel, 'input', 'change');
changed = true;
}
// Only proceed to Next if both values are set
if (genderSel.value !== CFG.subject.gender || ageSel.value !== ageStr) return false;
done.subject = true;
console.log('[PF] subject filled:', CFG.subject.gender, CFG.subject.age);
if (CFG.autoNext) {
setTimeout(() => {
const btn = form.querySelector('button[type="submit"]');
if (btn) { btn.click(); console.log('[PF] clicked Next (subject)'); }
}, 150);
}
return true;
}
// ── Step 3: Fill voter gender + age slider and optionally click Next ─────────
function handleTarget() {
if (!done.subject || done.target) return false;
const form = [...document.querySelectorAll('form.panel-target')]
.find(f => isVisible(f));
if (!form) return false;
// Gender radio
const radio = form.querySelector(
`input[name="target-gender"][value="${CFG.voters.gender}"]`
);
if (radio && !radio.checked) {
radio.closest('label').click();
}
// noUiSlider
const slider = form.querySelector('.pf-slider');
if (slider?.noUiSlider) {
const vals = slider.noUiSlider.get().map(Number);
if (vals[0] !== CFG.voters.ageSliderMin || vals[1] !== CFG.voters.ageSliderMax) {
slider.noUiSlider.set([CFG.voters.ageSliderMin, CFG.voters.ageSliderMax]);
}
}
done.target = true;
console.log('[PF] voters filled:', CFG.voters.gender, 'slider max:', CFG.voters.ageSliderMax);
if (CFG.autoNext) {
setTimeout(() => {
const btn = form.querySelector('button[type="submit"]');
if (btn) { btn.click(); console.log('[PF] clicked Next (target)'); }
}, 150);
}
return true;
}
// ── Step 4: Click the correct test size option ───────────────────────────────
function handleTestSize() {
if (!done.target || done.testSize) return false;
// Find a visible test-size panel
const panels = [...document.querySelectorAll('.panel-test-size-25')]
.filter(p => isVisible(p));
if (!panels.length) return false;
const targetLabel = CREDIT_LABEL[CFG.testSize];
if (!targetLabel) { console.warn('[PF] unknown testSize:', CFG.testSize); return false; }
for (const panel of panels) {
const options = panel.querySelectorAll('.menu-option-25');
for (const opt of options) {
const label = opt.querySelector('.size-label');
if (label && label.textContent.trim() === targetLabel) {
opt.click();
done.testSize = true;
console.log('[PF] clicked test size:', targetLabel);
return true;
}
}
}
return false;
}
// ── Main orchestrator ────────────────────────────────────────────────────────
function run() {
handleCategory();
handleSubject();
handleTarget();
handleTestSize();
}
// Debounced MutationObserver to catch Vue re-renders
let timer;
new MutationObserver(() => {
clearTimeout(timer);
timer = setTimeout(run, 250);
}).observe(document.documentElement, { childList: true, subtree: true });
setTimeout(run, 800);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment