demotivator
Version:
A TypeScript library containing 500+ hand-curated insults organized into themed packs, along with utilities to generate, search, and transform them.
78 lines • 2.75 kB
JavaScript
import { insults } from './insults';
import s from 'lodash/sample';
/**
* Scores how relevant an insult is to a given (pre-lowercased) search term.
*
* | Score | Condition |
* |-------|-----------|
* | 4 | Exact match (case-insensitive) |
* | 3 | Insult starts with the term |
* | 2 | Term appears as a whole word inside the insult |
* | 1 | Term appears anywhere as a substring |
* | 0 | No match |
*
* @internal
*/
const scoreInsult = (insult, normalizedTerm, wordBoundaryRegex) => {
const normalizedInsult = insult.toLowerCase();
if (normalizedInsult === normalizedTerm)
return 4;
if (normalizedInsult.startsWith(normalizedTerm))
return 3;
if (wordBoundaryRegex.test(insult))
return 2;
if (normalizedInsult.includes(normalizedTerm))
return 1;
return 0;
};
/**
* @returns a pseudorandom insult from the insult array.
*/
export default (array = insults) => {
const result = s(array);
if (!result)
throw new Error('No insults available');
return result;
};
/**
* Get a specific insult from a point and array that you specify
*
* @param {Insult[]} [array=insults] The array to select from. Default is original only
* @param {number} position The position in the array to select. Starts indexing at 1, not 0.
* @returns {Insult}
*/
export const insultAt = (position, array = insults) => {
if (!Number.isInteger(position))
throw new TypeError('Position must be an integer');
if (position < 1 || position > array.length)
throw new RangeError(`Position must be between 1 and ${array.length}`);
const result = array[position - 1];
if (!result)
throw new Error('No insults available');
return result;
};
export function searchInsults(term, array = insults, withPosition = false) {
if (array.length === 0)
throw new Error('No insults available');
const normalizedTerm = term.toLowerCase();
const escapedTerm = normalizedTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const wordBoundaryRegex = new RegExp(`\\b${escapedTerm}\\b`, 'i');
let bestMatch;
let bestIndex = -1;
let bestScore = 0;
for (let i = 0; i < array.length; i++) {
const insult = array[i];
const score = scoreInsult(insult, normalizedTerm, wordBoundaryRegex);
if (score > bestScore) {
bestScore = score;
bestMatch = insult;
bestIndex = i;
}
if (bestScore === 4)
break; // exact match — can't do better
}
if (!bestMatch)
throw new Error(`No insults found matching "${term}"`);
return withPosition ? { insult: bestMatch, position: bestIndex + 1 } : bestMatch;
}
//# sourceMappingURL=generateinsult.js.map