Skip to content

Instantly share code, notes, and snippets.

@peolic
Last active June 7, 2024 00:06
Show Gist options
  • Save peolic/9e2981a8a14a49b9626cb277f878b157 to your computer and use it in GitHub Desktop.
Save peolic/9e2981a8a14a49b9626cb277f878b157 to your computer and use it in GitHub Desktop.
IAFD helper userscript

IAFD Helper Userscript

Installation requires a browser extension such as Tampermonkey or Greasemonkey.

IAFD Helper Userstyle

Installation requires a browser extension such as Stylus.


Features

IAFD Helper Userscript

Performers

  • Birthday: ISO-8601 standard (partial dates too, when possible)
  • Height: Highlight the centimeters value
  • AKA: Copy names button (removes everything in parentheses)
  • Legacy performer ID and listed gender
  • Legacy performer page link - /person.rme/perfid=CheriDeVille/gender=f/cherie-deville.htm (instead of .../id=)

Titles

  • Scene Breakdowns: View performers for each scene
  • Release Date: ISO-8601 standard
  • Internal Film ID
  • Legacy title page link - /title.rme/title=measuring+up/year=2013/measuring-up.htm (instead of .../id=)

Studios / Distributors

  • Search field on selection pages
  • Button to export the list of scenes (CSV format)

General

  • Populate site search field on search results page
  • Link fixes (http:// -> https://)

IAFD Helper Userstyle

Titles

  • Reorder performers list, placing NonSex performers after "Sex" performers
  • Fade-out NonSex performers
  • Fix performer headshot styling
  • Improve scene breakdowns styling

Performers

  • Fix performer/director credits styling
  • Fix person update pages on small screens

Correction pages

  • Unrestrict width of correction confirmation text
/* ==UserStyle==
@name IAFD
@namespace https://github.com/peolic
@author peolic
@version 0.1.5
@homepageURL https://gist.github.com/peolic/9e2981a8a14a49b9626cb277f878b157
@updateURL https://gist.github.com/peolic/9e2981a8a14a49b9626cb277f878b157/raw/iafd.user.css
==/UserStyle== */
@-moz-document domain("www.iafd.com") {
/*************************
* iafd.com improvements *
*************************/
/**
* Title pages
*/
/* Fade-out NonSex performers */
.castbox.nonsex {
opacity: 60%;
transition: opacity .15s linear;
}
.castbox.nonsex:hover {
opacity: unset;
}
/* Place NonSex performers after "Sex" performers */
.padded-panel > .row > .col-sm-12 {
display: flex;
flex-wrap: wrap;
}
.castbox {
float: unset;
}
.castbox.nonsex {
order: 1;
}
/* Fix castbox styling */
.castbox {
margin-left: 0;
}
.castbox img.headshot {
margin-left: -14px;
}
/* Improve scene breakdowns styling */
#sceneinfo table td:first-of-type {
text-align: left;
padding: 8px 0 8px 1em;
width: 5.5em;
}
#sceneinfo table td[colspan="3"] {
padding: 8px 0 8px 0;
}
/**
* Person pages
*/
/* Fix performer credits styling */
#personal th[aria-label^="Notes:"] {
max-width: /*5*/7em;
}
#personal th[aria-label^="Formats :"] {
max-width: 3em;
}
/* Fix director credits styling */
#directoral th:first-child {
min-width: 10em;
max-width: 20em;
}
/**
* Correction pages
*/
/* Correction confirmation text width */
#cd .texty {
width: unset;
}
/**
* Person update pages
*/
/* Fix person update pages on small screens */
@media (max-width: 390px) {
.headshotrow {
padding-top: 0;
padding-bottom: 0;
--min-height: calc(46vw / 0.85);
}
.headshotrow > div {
min-height: calc(var(--min-height) + 4em);
padding: 0.5em;
}
.headshotrow:has(.pictag > br) > div {
min-height: calc(var(--min-height) + 5.5em);
}
.headshotrow > div img {
width: 46vw;
height: auto;
}
}
}
// ==UserScript==
// @name IAFD
// @author peolic
// @version 4.98
// @description IAFD helper
// @icon https://www.iafd.com/favicon-196x196.png
// @namespace https://github.com/peolic
// @match https://www.iafd.com/*
// @grant GM.setClipboard
// @grant GM.addStyle
// @homepageURL https://gist.github.com/peolic/9e2981a8a14a49b9626cb277f878b157
// @downloadURL https://gist.github.com/peolic/9e2981a8a14a49b9626cb277f878b157/raw/iafd.user.js
// @updateURL https://gist.github.com/peolic/9e2981a8a14a49b9626cb277f878b157/raw/iafd.user.js
// ==/UserScript==
function main() {
makeInternalLinksHTTPS();
fixIAFDLinks();
const { pathname } = window.location;
if (/^\/title\.(asp|rme\/)/.test(pathname))
return titlePage();
if (/^\/person\.(asp|rme\/)/.test(pathname))
return personPage();
if (/^\/(([a-z]{4}_)?studio|distrib)\.(asp|rme\/)$/.test(pathname))
return studioDistribSelectPage();
if (/^\/(studio|distrib)\.(asp\?|rme\/)(studio|distrib)=\d+/.test(pathname))
return studioDistribPage();
if (/^\/(new|updated)(perfs|headshots)\.asp/.test(pathname))
return personUpdatePage();
if (/^\/lookuptat\.asp/.test(pathname))
return tattooLookupPage();
if (/^\/results\.asp/.test(pathname))
return setSearchField();
}
const getBioDataElement = (headingText) => {
return Array.from(document.querySelectorAll('p.bioheading'))
.find(e => e.innerText.localeCompare(headingText, 'en', { sensitivity: 'accent' }) === 0)
?.nextElementSibling;
};
const makeBioEntry = (heading, ...data) => {
return [['heading', heading], ...data.map((d) => ['data', d])].map(([type, text]) => {
const p = document.createElement('p');
p.classList.add(`bio${type}`);
if (text instanceof Node) p.append(text);
else p.innerText = text;
return p;
});
};
const makeQuickSelect = (text) => {
const b = document.createElement('b');
b.style.userSelect = 'all';
b.innerText = text;
return b;
};
const makeISODateElement = (date) => {
const isoDate = new Date(`${date} 0:00 UTC`).toISOString().slice(0, 10);
return makeQuickSelect(isoDate);
};
const slug = (text, char='-') => {
return encodeURI(text.replace(/[^a-z0-9_.,:'-]+/gi, '___'))
.toLowerCase()
.replace(/___/g, char);
};
const makeLegacyTitleLink = (title, year) => {
const encodedTitle = slug(title, '+').replace(/^\+|\++$|^(a|an|the)\+/g, '');
return `https://www.iafd.com/title.rme/title=${encodedTitle}/year=${year}/${encodedTitle.replace(/\+/g, '-')}.htm`;
};
const makeLegacyPerformerLink = (perfId, gender, name=undefined) => {
const nameSlug = name ? `/${slug(name, '-')}.htm` : '';
return `https://www.iafd.com/person.rme/perfid=${perfId}/gender=${gender}${nameSlug}`;
};
function makeInternalLinksHTTPS() {
/** @type {NodeListOf<HTMLAnchorElement>} */
(document.querySelectorAll('a[href^="http://www.iafd.com/"]')).forEach((el) => {
el.href = el.href.replace(/^http:/, 'https:');
});
};
function fixIAFDLinks() {
const replacer = (_, offset, string) => {
if (offset > string.indexOf('/year=')) return '-';
else return '+';
};
/** @type {NodeListOf<HTMLAnchorElement>} */
(document.querySelectorAll('a[href*="//www.iafd.com/"]')).forEach((el) => {
el.href = el.href.replace(/%2f/g, replacer);
});
};
function titlePage() {
const canonical = document.querySelector('.panel:last-of-type .padded-panel')
?.innerText?.match(/should be linked to:\n(.+)/)?.[1]?.replace(/^http:/, 'https:');
if (canonical)
history.replaceState(null, '', canonical);
const titleHeading = document.querySelector('h1');
// remove trailing space
titleHeading.textContent = titleHeading.textContent.replace(/[ \n]$/, '');
const correct = document.querySelector('#correct');
if (correct) {
const filmIdBio = makeBioEntry('Film ID', correct.querySelector('input[name="FilmID"]')?.value);
filmIdBio[0].style.marginTop = '4em';
correct.before(...filmIdBio);
const links = [];
if (canonical) {
const link = document.createElement('a');
link.href = canonical;
link.title = link.href;
link.innerText = 'Title Page Link';
Object.assign(link.style, {
color: '#337ab7',
margin: '1em 0',
});
links.push(link);
}
// Legacy link
const titleAndYear = titleHeading.innerText.match(/^(.+) \((\d+)\)$/)?.slice(1) ?? [];
const legacyLink = document.createElement('a');
legacyLink.href = makeLegacyTitleLink(...titleAndYear);
legacyLink.title = legacyLink.href;
legacyLink.innerText = 'Legacy Title Page Link';
Object.assign(legacyLink.style, {
color: '#337ab7',
margin: '1em 0',
});
links.push(legacyLink);
correct.before(...makeBioEntry('🔗 Links', ...links));
}
const titleSeq = titleHeading.innerText.match(/\((X{0,3}(IX|IV|V?I{0,3}))\)/);
if (titleSeq) {
// Break up the text node
for (let i=0; i < titleHeading.childNodes.length; i++) {
if (i >= 10) break;
const node = titleHeading.childNodes[i];
const open = node.textContent.indexOf('(', 1),
close = node.textContent.indexOf(')');
if (!(open || close)) continue;
if (close && close+1 >= node.length) break;
node.splitText(open ?? close+1);
}
const seqNode = Array.from(titleHeading.childNodes).find((node) => node.textContent.includes(titleSeq?.[1]));
const seq = document.createElement('small');
seq.style.color = 'lightgray';
seq.textContent = seqNode.textContent;
titleHeading.replaceChild(seq, seqNode);
}
const releaseDateElement = getBioDataElement('RELEASE DATE');
const releaseDateNodes = Array.from(releaseDateElement.childNodes).filter((node) => node.nodeType === node.TEXT_NODE);
releaseDateNodes.forEach((node) => {
const text = node.textContent.trim();
const releaseDate = text.replace(/ \(.+\)$/, '');
if (!releaseDate || releaseDate === 'No Data')
return;
releaseDateElement.insertBefore(makeISODateElement(releaseDate), node);
releaseDateElement.insertBefore(document.createElement('br'), node);
});
setupScenePerformers();
}
const setupScenePerformers = () => {
const sceneInfo = document.querySelector('#sceneinfo');
if (!sceneInfo) return;
const scenes = Array.from(sceneInfo.querySelectorAll('table tr'));
if (scenes.length === 0) return;
const castboxes = Array.from(document.querySelectorAll('.castbox')).map((cb) => ({
name: cb.querySelector('a').lastChild.textContent,
cb,
}));
GM.addStyle(`
.ext__scene-performers-toggle { white-space: nowrap; }
.ext__scene-performers { display: flex; flex-wrap: wrap; column-gap: 1em; }
.ext__scene-performers .multiple-matches { background: #bf0000cc; color: white; flex-basis: 100%; font-weight: 600; margin: 0.5em 1em 0 0; padding: 0.25em 0.6em; }
.ext__scene-performers .castbox { max-width: 180px; min-height: unset; float: unset; margin-left: 0; }
.ext__scene-performers .castbox img.headshot { margin-left: -14px; }
`);
const toggleStates = new Array(scenes.length).fill(false);
const sceneToggles = scenes.map((sceneRow, sceneIndex) => {
const sceneLabel = sceneRow.querySelector(':scope > td:first-of-type');
const sceneLabelText = sceneLabel.innerText.toLowerCase();
const scenePerformers = sceneLabel.nextElementSibling;
const toggle = (newState = undefined) => {
let sceneContainer = sceneRow.querySelector('.ext__scene-performers');
if (sceneContainer) {
sceneContainer.style.display = sceneContainer.style.display === 'none' || newState === true ? '' : 'none';
toggleStates[sceneIndex] = sceneContainer.style.display !== 'none';
return;
} else if (newState === false) {
return;
}
const performers = scenePerformers.innerText
.match(/^.+$/m)[0] // grab first row
.replace(/ \[[a-z0-9 ]+\]/gi, '') // remove tags
.split(/, /g); // split names
sceneContainer = document.createElement('div');
sceneContainer.className = 'ext__scene-performers';
performers.forEach((performer) => {
const matches = castboxes.filter(({ name }) => name === performer);
if (matches.length === 0) {
const [count, text] = performer.match(/^(\d+) (.+?)s?$/)?.slice(1, 3) ?? [1, performer];
for (let i = 1; i <= count; i++) {
const node = document.createElement('div');
node.className = 'castbox';
const p = document.createElement('p');
const placeholder = document.createElement('div');
Object.assign(placeholder.style, { width: '170px', height: '200px', padding: '1em' });
const na = document.createElement('abbr');
na.title = `Performer "${performer}" was not found above`;
na.innerText = '[N/A]';
p.append(placeholder, `${count == 1 ? performer : text} `, na);
node.append(p);
sceneContainer.append(node);
}
} else {
if (matches.length > 1) {
const warning = document.createElement('div');
warning.innerText = `multiple matches for "${performer}"`;
warning.className = 'multiple-matches';
sceneContainer.prepend(warning);
}
matches.forEach(({ cb: performerCB }) => {
const node = performerCB.cloneNode(true);
// remove empty elements and extra line breaks from the inner paragraph element
for (const cn of Array.from(node.firstElementChild.childNodes).reverse()) {
if (cn.textContent.trim()) break;
cn.remove();
}
sceneContainer.append(node);
});
}
});
scenePerformers.append(sceneContainer);
toggleStates[sceneIndex] = true;
};
const sceneToggle = document.createElement('div');
sceneLabel.append(sceneToggle);
sceneToggle.append(sceneLabel.firstChild);
Object.assign(sceneToggle.style, { cursor: 'pointer', textDecoration: 'underline' });
sceneToggle.title = `View performers for ${sceneLabelText}`;
sceneToggle.className = 'ext__scene-performers-toggle';
sceneToggle.addEventListener('click', () => {
if (window.getSelection().type === 'Range') return; // Prevent unwanted action when selecting text
toggle();
});
return toggle;
});
// Toggle all scenes
const sceneInfoHeading = sceneInfo.querySelector('.panel-heading h3');
Object.assign(sceneInfoHeading.style, { cursor: 'pointer', textDecoration: 'double underline' });
sceneInfoHeading.title = `View performers for all ${scenes.length} scenes\n [hold Alt to reset]`;
sceneInfoHeading.addEventListener('click', (ev) => {
if (window.getSelection().type === 'Range') return; // Prevent unwanted action when selecting text
if (ev.altKey) {
sceneInfo.querySelectorAll('.ext__scene-performers').forEach((el) => el.remove());
return toggleStates.fill(false);
}
const newState = toggleStates.filter(Boolean).length < scenes.length;
sceneToggles.forEach((toggle) => toggle(newState));
});
};
function personPage() {
makeExportButton();
const canonical = document.querySelector('#perfwith a[href^="/person.rme/"]')?.href;
if (canonical)
history.replaceState(null, '', canonical);
const nameHeading = document.querySelector('h1');
// remove trailing space
nameHeading.textContent = nameHeading.textContent.replace(/[ \n]$/, '');
// Director page
if (/\/gender=d\//.test(window.location.pathname)) {
const directorPage = document.createElement('b');
directorPage.innerText = 'Director-only page:';
const [maleLink, femaleLink] = ['male', 'female'].map((gender) => {
const a = document.createElement('a');
a.href = (canonical ?? window.location.href).replace('/gender=d/', `/gender=${gender.charAt(0)}/`);
a.innerText = gender;
return a;
});
const directorHelp = document.createElement('div');
directorHelp.append(directorPage, ' try the ', maleLink, ' or ', femaleLink, ' performer pages');
nameHeading.after(directorHelp);
}
const corrections = document.querySelector('#corrections');
if (corrections) {
const perfIdCorrectionInput = corrections.querySelector('input[name="PerfID"]');
const gender = corrections.querySelector('input[name="Gender"]')?.value;
const perfIdScenePair = document.querySelector('#scenepairings')?.dataset.src.match(/\/perfid=(.+)$/)[1];
const perfId = perfIdCorrectionInput?.value ?? perfIdScenePair;
const perfIdBio = makeBioEntry('Performer ID', `${perfId} [${gender.toUpperCase()}]`);
perfIdBio[0].style.marginTop = '4em';
corrections.before(...perfIdBio);
if (!perfIdCorrectionInput.getAttribute('value')) {
corrections.querySelector('input[type="submit"]').style.color = 'red';
perfIdCorrectionInput.setAttribute('value', perfId);
}
const links = [];
if (canonical) {
const link = document.createElement('a');
link.href = canonical;
link.title = link.href;
link.innerText = 'Performer Page Link';
Object.assign(link.style, {
color: '#337ab7',
margin: '1em 0',
});
links.push(link);
}
// Legacy link
const legacyLink = document.createElement('a');
legacyLink.href = makeLegacyPerformerLink(perfId, gender, nameHeading.innerText);
legacyLink.title = legacyLink.href;
legacyLink.innerText = 'Legacy Performer Page Link';
Object.assign(legacyLink.style, {
color: '#337ab7',
margin: '1em 0',
});
links.push(legacyLink);
corrections.before(...makeBioEntry('🔗 Links', ...links));
}
const birthDateElement = getBioDataElement('BIRTHDAY');
const birthdayText = birthDateElement.innerText;
const birthDate = birthdayText.trim().match(/([A-Z][a-z]+ \d{1,2}, \d{4})\b/);
if (birthDate) {
birthDateElement.prepend(makeISODateElement(birthDate[1]), document.createElement('br'));
} else {
const partialDate = birthdayText.trim().match(/(?<month>\?\?|\d{1,2})\/(?<day>\?\?|\d{1,2})\/(?<year>\d{2}[\d?]{2})/);
if (partialDate) {
const { year, month, day } = partialDate.groups;
const exactYear = /^\d{4}$/.test(year);
if (exactYear) {
const dateParts = [year, month, day].join('-');
const firstQM = dateParts.indexOf('?', 4)-1;
const [isoDate, remainder] = [dateParts.slice(0, firstQM), dateParts.slice(firstQM)];
birthDateElement.prepend(makeQuickSelect(isoDate), remainder, document.createElement('br'));
}
if (month !== '??') {
const partialDateStr = (new Date(birthdayText.replace(/\?\?/g, '01') + ' 12:00'))
.toLocaleString('en-us', {
month: month === '??' ? undefined : 'long',
day: day === '??' ? undefined : 'numeric',
year: exactYear ? 'numeric' : undefined,
}) + (exactYear ? '' : `, ${year}`);
birthDateElement.lastChild.before(document.createTextNode(partialDateStr), document.createElement('br'));
}
}
}
const heightElement = getBioDataElement('HEIGHT');
const height = heightElement?.innerText.trim().match(/\((\d+) cm\)/);
if (height && heightElement) {
heightElement.prepend(makeQuickSelect(height[1]), document.createElement('br'));
}
// example: Lee Stone
const akasDirectorElement = getBioDataElement('DIRECTOR AKA');
if (akasDirectorElement && akasDirectorElement.innerText.match(/^None No known aliases$/)) {
akasDirectorElement.innerText = akasDirectorElement.innerText.replace(/^None /, '');
}
const akasElement = getBioDataElement('AKA') ?? getBioDataElement('PERFORMER AKA');
const akas = akasElement?.innerText.trim();
if (akas && akas !== 'No known aliases') {
const copyButtonDefaultText = '[copy names]';
const akasCopy = document.createElement('a');
akasCopy.innerText = copyButtonDefaultText;
akasCopy.title = 'Copy only the names used (removes site names)'
akasCopy.id = 'copy-akas';
Object.assign(akasCopy.style, { float: 'right', cursor: 'pointer', lineHeight: 1 });
akasCopy.addEventListener('click', async (ev) => {
ev.stopPropagation();
ev.preventDefault();
// https://regex101.com/r/7Ad3U1/2
const names = akas.replace(/ \(.+?\)/g, '').split(/[,;] ?/g).map((s) => s.trim()).filter(Boolean);
if (!names || names.length === 0) {
akasCopy.innerText = '❌ Failed!';
akasCopy.style.color = 'red';
return;
}
const result = names.join(', ');
GM.setClipboard(result);
akasCopy.innerText = '✔ Copied to clipboard';
akasCopy.style.color = 'green';
akasElement.innerText = result;
akasElement.style.backgroundColor = 'yellow';
await wait(1500);
akasCopy.innerText = copyButtonDefaultText;
akasCopy.style.color = null;
akasElement.innerText = akas;
akasElement.style.backgroundColor = '';
})
akasElement.previousElementSibling.append(akasCopy);
}
}
function studioDistribSelectPage() {
const select = document.querySelector('select[name="Studio"], select[name="Distrib"]');
const pageType = select.closest('form').getAttribute('action').replace(/^\/|\.rme\/$/g, '').toLowerCase();
const selectType = select.name.toLowerCase();
const fullType = selectType === 'distrib' ? 'distributor' : selectType;
const listId = `${selectType}-list`;
const submit = document.querySelector('form input[type="submit"]');
const input = document.createElement('input');
input.type = 'text';
input.placeholder = `Lookup ${fullType}...`;
input.setAttribute('list', listId);
Object.assign(input.style, {
display: 'block',
width: window.getComputedStyle(select).width,
marginBottom: '.5rem',
});
select.before(input);
input.focus();
const datalist = document.createElement('datalist');
datalist.id = listId;
for (const option of select.children) {
const cloned = option.cloneNode(true);
cloned.removeAttribute('value');
datalist.append(cloned);
}
select.before(datalist);
const escapeRegex = (string) => {
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
};
input.addEventListener('input', () => {
const value = input.value.trim();
if (!value) return;
const search = new RegExp(`^${escapeRegex(value)}`, 'i');
const found = Array.from(select.children).find((o) => search.test(o.innerText));
if (!found) return;
select.value = found.value;
});
const handleClick = (/** @type {Event} */ ev) => {
if (!select.value)
return;
ev.preventDefault();
ev.stopPropagation();
const displayName = select.selectedOptions[0].textContent.replace(/[^a-z0-9.-]/ig, '-');
window.location = `https://www.iafd.com/${pageType}.rme/${selectType}=${select.value}/${displayName}.htm`;
};
select.addEventListener('dblclick', (ev) => {
if (ev.target instanceof HTMLOptionElement)
return handleClick(ev);
});
select.addEventListener('keyup', (/** @type {KeyboardEvent} */ ev) => {
if (ev.key === 'Enter')
return handleClick(ev);
});
submit.addEventListener('click', handleClick);
}
function studioDistribPage() {
makeExportButton();
}
async function makeExportButton() {
const filter = await elementReady('div[id$="_filter"]');
const type = filter.id.split('_')[0];
const exportButtonDefaultText = 'Export CSV';
const exportTimestamp = (new Date()).toISOString();
const tools = document.createElement('div');
Object.assign(tools.style, {
marginRight: '.5em',
display: 'inline-block',
});
filter.prepend(tools);
const button = document.createElement('button');
button.type = 'button';
button.innerText = exportButtonDefaultText;
button.style.marginRight = '.5em';
tools.prepend(button);
(async () => {
const info = await Promise.race([
elementReady(`div#${type}_info`).then((el) => /** @type {HTMLDivElement} */ (el).innerText),
wait(5000).then(() => null),
]);
if (!info) return;
/** @param {string} s */
const toNumber = (s) => Number(s?.replace(/,/, ''));
const { start, end, total } = info
.match(/Showing (?<start>[\d,]+) to (?<end>[\d,]+) of (?<total>[\d,]+) entries/i)
?.groups || {};
const count = toNumber(end) - Math.max(toNumber(start) - 1, 0);
const totalCount = toNumber(total);
let countLabel = `Count: ${count}`;
if (count !== totalCount)
countLabel += ` out of ${total}`;
button.title = countLabel;
})();
button.addEventListener('click', async () => {
const output = makeOutput(type);
if (!output) {
button.innerText = '❌ Failed!';
button.style.backgroundColor = 'red';
return;
}
GM.setClipboard(output);
button.innerText = '✔ Copied to clipboard';
button.style.backgroundColor = 'yellow';
await wait(1500);
button.innerText = exportButtonDefaultText;
button.style.backgroundColor = '';
});
/** @param {string} type */
const makeOutput = (type) => {
const dataRows = Array.from(document.querySelectorAll(`div#${type}_wrapper .dataTable tbody > tr`));
let columns;
let csv;
if (type === 'studio' || type === 'distable') {
const data = dataRows.map((tr) => ({
url: tr.children[0].querySelector('a').href,
title: tr.children[0].innerText,
studio: tr.children[1].innerText,
year: Number(tr.children[2].innerText),
}));
columns = ['Title', 'Studio', 'Year', 'URL'];
csv = data.map((d) => columns.map((c) => d[c.toLowerCase()]).join('\t'));
} else if (type === 'personal') {
const data = dataRows.map((tr) => ({
url: tr.children[0].querySelector('a').href,
title: tr.children[0].innerText,
year: Number(tr.children[1].innerText),
distributor: tr.children[2].innerText,
notes: tr.children[3].innerText,
}));
columns = ['Title', 'Distributor', 'Year', /*'Notes', */'URL'];
csv = data.map((d) => columns.map((c) => d[c.toLowerCase()]).join('\t'));
} else {
return null;
}
return columns.join('\t') +`\t${exportTimestamp}` + '\n' + csv.join('\n') + '\n';
};
}
function personUpdatePage() {
}
async function tattooLookupPage() {
const info = await Promise.race([
elementReady(`div[id^="tat"][id$="_info"]`).then((el) => /** @type {HTMLDivElement} */ (el).innerText),
wait(5000).then(() => null),
]);
if (!info) return;
}
function setSearchField() {
const field = document.querySelector('#the-basics > input[name="searchstring"]');
const params = new URLSearchParams(window.location.search);
field.value = params.get('searchstring');
}
const wait = (/** @type {number} */ ms) => new Promise((resolve) => setTimeout(resolve, ms));
/**
* Waits for an element satisfying selector to exist, then resolves promise with the element.
* Useful for resolving race conditions.
*
* @param {string} selector
* @param {HTMLElement} [parentEl]
* @returns {Promise<Element>}
*/
function elementReady(selector, parentEl) {
return new Promise((resolve, reject) => {
let el = (parentEl || document).querySelector(selector);
if (el) {resolve(el);}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from((parentEl || document).querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
});
})
.observe(parentEl || document.documentElement, {
childList: true,
subtree: true
});
});
}
main();
@janvier56
Copy link

Can you add a description of the functions for the userscript?

@janvier56
Copy link

janvier56 commented Jun 5, 2024

Thanks for the update of the description!
Good job for useful functions.

My 2 cents:

USERSCRIPT - IAFD Helper Userscript
Performers:

USERSTYLE - IAFD Helper Userstyle
Reorder performers list, placing NonSex performers after "Sex" performers AND Fade-out NonSex performers:
Go0d idea!

@peolic
Copy link
Author

peolic commented Jun 6, 2024

i don't see the copy button and the parentheses are still present.

the button is on the same line as Performer AKA, and the parentheses are removed when it's copied to the clipboard (not on the page).
image

@janvier56
Copy link

Haa yes, thanks!

Perfect:
i wanted do that (removes the parentheses) with CSS, but was not possible.

@janvier56
Copy link

janvier56 commented Jun 6, 2024

I notice a strange thing about the "Copy" Button.

With my userstyle:
IAFD - Widescreen Dark And Gray

i see (and can use it) the button as well.
but if i reload the same page, the button is gone.

if i search in the Dev tool, no button too.

its CSS selector match nothing:
p.headshotcaption + p.bioheading a[title="Copy only the names used (removes site names)"]
OR
p.bioheading a[title="Copy only the names used (removes site names)"]

If disable my userstyles and reload the page, the button is here!
I can re enable my userstyle now (without reload the page) and it stay present...

Strange:
never seen a CSS which can break a javascript function before.
Do you understand where is the problem??

For info:
My test was done with Chrome

@peolic
Copy link
Author

peolic commented Jun 6, 2024

fixed. it was due to the Vital Stats tab being hidden. (Height was not being transformed by CSS to uppercase HEIGHT, so it didn't match)
if you need to change the button's CSS, you can use #copy-akas selector now.

@janvier56
Copy link

Hooo, what a quick fix:
thanks work fine.

Outside this problem:
why when i wanted update your script , Tampermonkey don't find your new version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment