- // ==UserScript==
- // @name Auto-select advertisements
- // @namespace https://github.com/f1r3w4rr10r/fa-utils
- // @version 0.2.2
- // @description This automatically selects submission notifications, that are advertisements.
- // @author f1r3w4rr10r
- // @match https://www.furaffinity.net/msg/submissions/*
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
- // @license MIT
- // @grant none
- // ==/UserScript==
-
- (async function () {
- "use strict";
-
- const DEFINITELY_AD_THRESHOLD = 50;
- const AMBIGUOUS_AD_THRESHOLD = 25;
-
- // The second "c" is a Cyrillic "s";
- const COMMISSION_REGEX_STRING = "[cс]omm?(?:ission)?s?";
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_COMMISSION_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bauction\b/i },
- {
- target: "name",
- pattern: new RegExp(`(?:^|\\W)${COMMISSION_REGEX_STRING}\\b`, "i"),
- },
- { target: "name", pattern: /\bwing.its?\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_DISCOUNTS_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bdiscount\b/i },
- { target: "name", pattern: /\bsale\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_MEMBERSHIPS_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bboosty\b/i },
- { target: "name", pattern: /\bp[@a]treon\b/i },
- { target: "name", pattern: /\bsub(?:scribe)?\s*star\b/i },
- { target: "name", pattern: /\bsupporters?\b/i },
- { target: "tags", pattern: /\bboosty\b/i },
- { target: "tags", pattern: /\bp[@a]treon\b/i },
- { target: "tags", pattern: /\bsub(?:scribe)?\s*star\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_PRICE_LIST_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bprice\s+(?:list|sheet)\b/i },
- { target: "name", pattern: /\bcommission\s+info/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_RAFFLES_SELECTOR = { target: "name", pattern: /\braffle\b/i };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_SHOPS_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bshop\b/i },
- { target: "name", pattern: /\bfurplanet\b/i },
- { target: "description", pattern: /\bfurplanet\b/i },
- { target: "tags", pattern: /\bfurplanet\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_SLOTS_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\b(?:multi)?slots?\b/i },
- { target: "name", pattern: /\bart\s+marathon\b/i },
- { target: "description", pattern: /\d\s+slots\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_STREAM_SELECTOR = {
- target: "name",
- pattern: /\b(?:live)?stream\b/i,
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_TEASER_SELECTOR = {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bpreview\b/i },
- { target: "name", pattern: /\bspoiler\b/i },
- { target: "name", pattern: /\bteaser\b/i },
- ],
- };
-
- /** @type {AdSelector | SelectorCombiner} */
- const AMBIGUOUS_YCH_SELECTOR = {
- operator: "or",
- operands: [
- {
- target: "name",
- pattern: /\by\s*c\s*h\s*s?\b/i,
- },
- {
- target: "description",
- pattern: /\by\s*c\s*h\s*s?\b/i,
- },
- ],
- };
-
- /** @type {AdRules} */
- const adRules = [
- {
- ruleName: "adoptables",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\badopt(?:(?:able)?s?|ing|ion)\b/i },
- { target: "name", pattern: /\bcustoms\b/i },
- { target: "tags", pattern: /\badopt(?:(?:able)?s?|ing|ion)\b/i },
- {
- target: "description",
- pattern: /\badopt(?:(?:able)?s?|ing|ion)\b/i,
- },
- ],
- },
- },
- {
- ruleName: "commission ads (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_COMMISSION_SELECTOR,
- },
- {
- ruleName: "commission ads (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_COMMISSION_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bclosed\b/i },
- { target: "name", pattern: /\bhalfbody\b/i },
- { target: "name", pattern: /\bopen(?:ed)?\b/i },
- { target: "name", pattern: /\bsale\b/i },
- { target: "name", pattern: /\bslots?\b/i },
- { target: "name", pattern: /\bych\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "convention dealers",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "tags", pattern: /\bdealers?\s+den\b/i },
- { target: "description", pattern: /\bdealers?\s+den\b/i },
- ],
- },
- },
- {
- ruleName: "discounts (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_DISCOUNTS_SELECTOR,
- },
- {
- ruleName: "discounts (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_DISCOUNTS_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\$/ },
- { target: "name", pattern: /\bbase\b/i },
- { target: "name", pattern: /\bclaimed\b/i },
- { target: "name", pattern: /\b(?:multi)?slot\b/i },
- { target: "name", pattern: /\boffer\b/i },
- { target: "name", pattern: /\bprice\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "memberships (ambgiuous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_MEMBERSHIPS_SELECTOR,
- },
- {
- ruleName: "memberships (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- {
- operator: "or",
- operands: [
- ...AMBIGUOUS_MEMBERSHIPS_SELECTOR.operands,
- { target: "description", pattern: /\bboosty\b/i },
- { target: "description", pattern: /\bp[@a]treon\b/i },
- { target: "description", pattern: /\bsub(?:scribe)?\s*star\b/i },
- ],
- },
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bdiscount\b/i },
- { target: "name", pattern: /\b(?:available|now)\s+on\b/i },
- { target: "name", pattern: /\bposted\s+to\b/i },
- { target: "name", pattern: /\bpreview\b/i },
- { target: "name", pattern: /\bteaser?\b/i },
- { target: "name", pattern: /\bup\s+on\b/i },
- { target: "name", pattern: /\bis\s+up\b/i },
- { target: "description", pattern: /\bup\s+on\b/i },
- { target: "description", pattern: /\bfull.*\s+on\b/i },
- { target: "description", pattern: /\bexclusive(?:ly)?.+for\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "price lists (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_PRICE_LIST_SELECTOR,
- },
- {
- ruleName: "price lists (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_PRICE_LIST_SELECTOR,
- {
- operator: "or",
- operands: [],
- },
- ],
- },
- },
- {
- ruleName: "raffles (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_RAFFLES_SELECTOR,
- },
- {
- ruleName: "raffles (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_RAFFLES_SELECTOR,
- { target: "name", pattern: /\bwinners?\b/i },
- ],
- },
- },
- {
- ruleName: "reminders",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\breminder+\b/i },
- { target: "name", pattern: /^REM$/ },
- ],
- },
- },
- {
- ruleName: "shops (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_SHOPS_SELECTOR,
- },
- {
- ruleName: "shops (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- {
- operator: "and",
- operands: [
- AMBIGUOUS_SHOPS_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bprint\b/i },
- { target: "description", pattern: /\bup\s+on\b/i },
- ],
- },
- ],
- },
- { target: "tags", pattern: /\bmerch\b/i },
- ],
- },
- },
- {
- ruleName: "slots (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_SLOTS_SELECTOR,
- },
- {
- ruleName: "slots (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_SLOTS_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bavailable\b/i },
- { target: "name", pattern: /\bopen\b/i },
- { target: "name", pattern: /\bsketch\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "stream ads (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_STREAM_SELECTOR,
- },
- {
- ruleName: "stream ads (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bpicarto\.tv\b/i },
- { target: "name", pattern: /\bstreaming\b/i },
- {
- operator: "and",
- operands: [
- AMBIGUOUS_STREAM_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\blive\b/i },
- { target: "name", pattern: /\boffline\b/i },
- { target: "name", pattern: /\bonline\b/i },
- { target: "name", pattern: /\bpreorders?\b/i },
- { target: "name", pattern: /\bslots?\b/i },
- { target: "name", pattern: /\bup\b/i },
- ],
- },
- ],
- },
- {
- operator: "and",
- operands: [
- { target: "name", pattern: /\bstream\b/i },
- { target: "tags", pattern: /\bstream\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "teasers (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_TEASER_SELECTOR,
- },
- {
- ruleName: "teasers (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_TEASER_SELECTOR,
- {
- target: "description",
- pattern:
- /\b(?:available|n[eo]w|out)\b.*\bon\b.*\b(?:boosty|p[@a]treon|sub(?:scribe)?\s*star)\b/i,
- },
- ],
- },
- },
- {
- ruleName: "WIPs",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- { target: "name", pattern: /\bwip\b/i },
- { target: "tags", pattern: /\bwip\b/i },
- ],
- },
- },
- {
- ruleName: "YCHs (ambiguous)",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: AMBIGUOUS_YCH_SELECTOR,
- },
- {
- ruleName: "YCHs (definitive)",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "and",
- operands: [
- {
- operator: "or",
- operands: [
- AMBIGUOUS_YCH_SELECTOR,
- { target: "name", pattern: /^closed$/i },
- ],
- },
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bauction\b/i },
- { target: "name", pattern: /\bavailable\b/i },
- { target: "name", pattern: /\bdiscount\b/i },
- { target: "name", pattern: /\bmultislot\b/i },
- { target: "name", pattern: /\bo\s+p\s+e\s+n\b/i },
- { target: "name", pattern: /\bprice\b/i },
- { target: "name", pattern: /\bpreview\b/i },
- { target: "name", pattern: /\braffle\b/i },
- { target: "name", pattern: /\brem(?:ind(?:er)?)?\d*\b/i },
- { target: "name", pattern: /\brmd\b/i },
- { target: "name", pattern: /\bsale\b/i },
- { target: "name", pattern: /\bslots?\b/i },
- { target: "name", pattern: /\bsold\b/i },
- { target: "name", pattern: /\btaken\b/i },
- { target: "name", pattern: /\busd\b/i },
- { target: "name", pattern: /\b\$\d+\b/i },
- { target: "tags", pattern: /^$/ },
- { target: "tags", pattern: /\bauction\b/i },
- { target: "tags", pattern: /\bsale\b/i },
- { target: "tags", pattern: /\bych\b/i },
- { target: "description", pattern: /\bSB|starting\s+bid\b/i },
- {
- target: "description",
- pattern: /https?:\/\/ych\.art\/auction\/\d+/i,
- },
- ],
- },
- ],
- },
- },
- {
- ruleName: "misc ambiguous",
- value: AMBIGUOUS_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bart\s+pack\b/i },
- { target: "name", pattern: /\bart.+earlier\b/i },
- { target: "name", pattern: /\bavailable\s+now\b/i },
- { target: "name", pattern: /\bclosed\b/i },
- { target: "name", pattern: /\bopen\b/i },
- { target: "name", pattern: /\bpoll\b/i },
- { target: "name", pattern: /\brem\b/i },
- { target: "name", pattern: /\bsold\b/i },
- { target: "name", pattern: /\bwip\b/i },
- { target: "tags", pattern: /\bteaser\b/i },
- { target: "description", pattern: /\brules:/i },
- ],
- },
- },
- {
- ruleName: "misc definitive",
- value: DEFINITELY_AD_THRESHOLD,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bcharacters?\s+for\s+sale\b/i },
- { target: "description", pattern: /\bpoll\s+is\s+up\b/i },
- ],
- },
- },
- {
- ruleName: "comic pages",
- value: -200,
- selector: {
- operator: "or",
- operands: [
- {
- target: "name",
- pattern: /\bpage\s+\d+/i,
- },
- ],
- },
- },
- {
- ruleName: "commission only names",
- value: -200,
- selector: {
- operator: "or",
- operands: [
- {
- target: "name",
- pattern: new RegExp(`\\[${COMMISSION_REGEX_STRING}\\]`, "i"),
- },
- {
- target: "name",
- pattern: new RegExp(`^${COMMISSION_REGEX_STRING}$`, "i"),
- },
- ],
- },
- },
- {
- ruleName: "completed YCHs",
- value: -200,
- selector: {
- operator: "and",
- operands: [
- AMBIGUOUS_YCH_SELECTOR,
- {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bfinished\b/i },
- { target: "name", pattern: /\bresult\b/i },
- { target: "description", pattern: /\bfinished\b/i },
- ],
- },
- ],
- },
- },
- {
- ruleName: "user reference",
- value: -200,
- selector: {
- operator: "or",
- operands: [
- { target: "name", pattern: /\bfor\s+[\w\-.]+/i },
- {
- target: "description",
- pattern:
- /\b(?:by|for|from):?\s+(?::(?:icon[\w\-.]+|[\w\-.]+icon):|@?@\w+)/i,
- },
- {
- target: "description",
- pattern: /^:(?:icon[\w\-.]+|[\w\-.]+icon):$/i,
- },
- { target: "description", pattern: /^(?:by|for|from)\s+\w+/i },
- { target: "description", pattern: /\bych\s+for\s+\w+/i },
- { target: "description", pattern: /\bcharacter\s+©\s+\w+/i },
- {
- target: "description",
- pattern: /\bcharacter\s+belongs\s+to\s+@?@\w+/i,
- },
- { target: "description", pattern: /\bcommission\s+for\b$/im },
- {
- target: "description",
- pattern:
- /(?:©|\(c\))\s*(?::(?:icon[\w\-.]+|[\w\-.]+icon):|@?@\w+)$/im,
- },
- {
- target: "description",
- pattern: /\bpatreon\s+reward\s+for\s+\w+/i,
- },
- ],
- },
- },
- {
- ruleName: "artist reference",
- value: -200,
- selector: {
- operator: "or",
- operands: [
- {
- target: "description",
- pattern: /🎨\s*:\s*(?::(?:icon[\w\-.]+|[\w\-.]+icon):|@?@\w+)/i,
- },
- ],
- },
- },
- {
- ruleName: "rewards",
- value: -200,
- selector: {
- operator: "or",
- operands: [{ target: "description", pattern: /\breward\s+sketch\b/i }],
- },
- },
- ];
-
- /**
- * An evaluator for {@link AdRules} on a submission.
- */
- class AdRulesEvaluator {
- /**
- * Create a new {@link AdRulesEvaluator}.
- * @param {AdRules} rules - the rules to use
- */
- constructor(rules) {
- this.adRuleEvaluators = rules.map((r) => new AdRuleEvaluator(r));
- }
-
- /**
- * Explain the rating of a submission.
- * @param {SubmissionData} submissionData - the data of the submission to explain
- */
- explain(submissionData) {
- const result = this.test(submissionData);
-
- console.group(
- `Submission: "${submissionData.name}" ${result.rating} -> "${result.level}"`,
- );
-
- this.adRuleEvaluators.forEach((o) => o.explain(submissionData));
-
- console.groupEnd();
- }
-
- /**
- * Test a submission against the rules of the evaluator.
- * @param {SubmissionData} submissionData - the data of the submission to test
- * @returns {AdRulesResult} the rating result
- */
- test(submissionData) {
- const values = this.adRuleEvaluators
- .map((e) => e.test(submissionData))
- .filter((e) => e !== null);
-
- const rating = values.reduce((t, v) => t + v, 0);
-
- /** @type {AdvertisementLevel | null} */
- let level = null;
- if (rating >= DEFINITELY_AD_THRESHOLD) level = "advertisement";
- else if (rating >= AMBIGUOUS_AD_THRESHOLD) level = "ambiguous";
-
- return { level, rating };
- }
- }
-
- /**
- * Map a selector tree node to an evaluator instance.
- * @param {AdSelector | SelectorCombiner} selector
- * @return {AdSelectorEvaluator | SelectorCombinerEvaluator}
- */
- function mapSelectorToEvaluator(selector) {
- if ("target" in selector) return new AdSelectorEvaluator(selector);
- return new SelectorCombinerEvaluator(selector);
- }
-
- /**
- * An evaluator for a single {@link AdRule} on a submission
- */
- class AdRuleEvaluator {
- /**
- * Create a new {@link AdRuleEvaluator}.
- * @param {AdRule} rule - the rule to use
- */
- constructor({ ruleName, value, selector }) {
- this.ruleName = ruleName;
- this.value = value;
- this.selectorEvaluator = mapSelectorToEvaluator(selector);
- }
-
- /**
- * Explain the rating of a submission.
- * @param {SubmissionData} submissionData - the data of the submission to explain
- */
- explain(submissionData) {
- const matches = this.selectorEvaluator.test(submissionData);
-
- const groupName = `Rule "${this.ruleName}"`;
- if (matches) {
- console.group(groupName + ` matches: ${this.value}`);
- } else {
- console.groupCollapsed(groupName);
- }
-
- this.selectorEvaluator.explain(submissionData);
-
- console.groupEnd();
- }
-
- /**
- * Test a submission against the rules of the evaluator.
- * @param {SubmissionData} submissionData - the data of the submission to test
- * @returns {number | null} the value of the rule, when there's a match; null otherwise
- */
- test(submissionData) {
- if (this.selectorEvaluator.test(submissionData)) return this.value;
- return null;
- }
- }
-
- /**
- * Map the operands of a selector combiner to evaluators.
- * @param {(AdSelector | SelectorCombiner)[]} operands
- * @return {(AdSelectorEvaluator | SelectorCombinerEvaluator)[]}
- */
- function mapOperandsToEvaluators(operands) {
- return operands.map((o) => mapSelectorToEvaluator(o));
- }
-
- /**
- * An evaluator for a {@link SelectorCombiner} on a submission
- */
- class SelectorCombinerEvaluator {
- /**
- * @param {SelectorCombiner} combiner
- */
- constructor({ operator, operands }) {
- this.operator = operator;
- this.operandEvaluators = mapOperandsToEvaluators(operands);
- }
-
- /**
- * Explain the rating of a submission.
- * @param {SubmissionData} submissionData - the data of the submission to explain
- */
- explain(submissionData) {
- const matches = this.test(submissionData);
-
- const groupName = `Combiner "${this.operator}"`;
- if (matches) {
- console.group(groupName + " matches");
- } else {
- console.groupCollapsed(groupName);
- }
-
- for (const o of this.operandEvaluators) {
- const matched = o.explain(submissionData);
- if (matched && this.operator === "or") break;
- }
-
- console.groupEnd();
- }
-
- /**
- * Test a submission against the rules of the combiner's operands.
- * @param {SubmissionData} submissionData - the data of the submission to test
- * @returns {boolean} whether the combiner in total matches the submission
- */
- test(submissionData) {
- switch (this.operator) {
- case "and":
- return this.operandEvaluators.every((o) => o.test(submissionData));
- case "or":
- return this.operandEvaluators.some((o) => o.test(submissionData));
- }
- }
- }
-
- /**
- * An evaluator for an {@link AdSelector} on a submission
- */
- class AdSelectorEvaluator {
- /**
- * @param {AdSelector} selector
- */
- constructor({ target, pattern }) {
- this.target = target;
- this.pattern = pattern;
- }
-
- /**
- * Explain the rating of a submission.
- * @param {SubmissionData} submissionData - the data of the submission to explain
- * @return {boolean} whether the submission matched the selector
- */
- explain(submissionData) {
- const target = this.#getTargetString(submissionData);
- const matched = this.pattern.test(target);
- console.log(this.pattern, matched, target);
- return matched;
- }
-
- /**
- * Test a submission against the rules of the combiner's operands.
- * @param {SubmissionData} submissionData - the data of the submission to test
- * @returns {boolean} whether the selector matches the submission
- */
- test(submissionData) {
- return this.pattern.test(this.#getTargetString(submissionData));
- }
-
- /**
- * @param {SubmissionData} submissionData
- * @return {string}
- */
- #getTargetString(submissionData) {
- switch (this.target) {
- case "name":
- return submissionData.name;
- case "tags":
- return submissionData.tags;
- case "description":
- return submissionData.description;
- }
- }
- }
-
- /**
- * Iterate over all submissions on a page and test the ad rules against them.
- * @returns {[number, number, number]} number of ads, number of ambiguous ads, number of untagged submissions
- */
- function iterateSubmissions() {
- const figures = Array.from(
- document.querySelectorAll("section.gallery figure"),
- );
- let advertisements = 0;
- let ambiguous = 0;
- let untagged = 0;
-
- const evaluator = new AdRulesEvaluator(adRules);
-
- for (const figure of figures) {
- const figcaption = figure.querySelector("figcaption");
- const checkbox = figure.querySelector("input");
- const nameAnchor = figcaption?.querySelector("a");
- const submissionName = nameAnchor?.textContent;
- const tags = figure?.querySelector("img")?.dataset["tags"];
- const description = descriptions[checkbox?.value ?? ""]?.description;
-
- const submissionData = {
- name: submissionName ?? "",
- description: description ?? "",
- tags: tags ?? "",
- };
-
- const result = evaluator.test(submissionData);
-
- const button = document.createElement("button");
- button.type = "button";
- button.textContent = `Ad-rating: ${result.rating}`;
- button.addEventListener("click", () => evaluator.explain(submissionData));
- checkbox?.parentElement?.appendChild(button);
-
- if (result.level === "advertisement") {
- figure.classList.add("advertisement");
- if (checkbox) checkbox.checked = true;
- advertisements += 1;
- } else if (result.level === "ambiguous") {
- figure.classList.add("maybe-advertisement");
- ambiguous += 1;
- }
-
- if (tags === "") {
- figcaption?.classList.add("not-tagged");
- untagged += 1;
- }
- }
-
- return [advertisements, ambiguous, untagged];
- }
-
- const sheet = new CSSStyleSheet();
- sheet.replaceSync(
- `
- figure.advertisement { outline: orange 3px solid; }
- figure.maybe-advertisement { outline: yellow 3px solid; }
- figcaption.not-tagged input { outline: orange 3px solid; }
- figcaption button { line-height: 1; margin-left: 1rem; padding: 0; }
- `.trim(),
- );
- document.adoptedStyleSheets.push(sheet);
-
- const sectionHeader = document.querySelector(".section-header");
-
- const advertisementsSelectMessage = document.createElement("p");
- sectionHeader?.appendChild(advertisementsSelectMessage);
-
- const [advertisements, ambiguous, untagged] = iterateSubmissions();
-
- const message = `Selected ${advertisements} advertisement and ${ambiguous} ambiguous submissions. ${untagged} submissions were not tagged.`;
-
- advertisementsSelectMessage.textContent = message;
- })();