1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-24 07:12:57 +00:00

Merge pull request #67 from koloml/feature/option-to-auto-remove-invalid-tags

Detect invalid blacklisted tags from Furbooru and allow to remove them manually or automatically
This commit is contained in:
2024-12-16 16:32:44 +04:00
committed by GitHub
10 changed files with 225 additions and 2 deletions

66
src/config/tags.ts Normal file
View File

@@ -0,0 +1,66 @@
export const tagsBlacklist: string[] = [
"anthro art",
"anthro artist",
"anthro cute",
"anthro furry",
"anthro nsfw",
"anthro oc",
"anthroart",
"anthroartist",
"anthrofurry",
"anthronsfw",
"anthrooc",
"art",
"artist",
"artwork",
"cringe",
"cringeworthy",
"cute art",
"cute artwork",
"cute furry",
"downvotes galore",
"drama in comments",
"drama in the comments",
"fandom",
"furries",
"furry anthro",
"furry art",
"furry artist",
"furry artwork",
"furry character",
"furry community",
"furry cute",
"furry fandom",
"furry nsfw",
"furry oc",
"furryanthro",
"furryart",
"furryartist",
"furryartwork",
"furrynsfw",
"furryoc",
"image",
"no tag",
"not tagged",
"notag",
"notags",
"nsfw anthro",
"nsfw art",
"nsfw artist",
"nsfw artwork",
"nsfw",
"nsfwanthro",
"nsfwart",
"nsfwartist",
"nsfwartwork",
"paywall",
"rcf community",
"sfw",
"solo oc",
"tag me",
"tag needed",
"tag your shit",
"tagme",
"upvotes galore",
"wall of faves"
];

View File

@@ -3,6 +3,16 @@ import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI.js";
import {tagsBlacklist} from "$config/tags.ts";
class BlackListedTagsEncounteredError extends Error {
/**
* @param {string} tagName
*/
constructor(tagName) {
super(`This tag is blacklisted and prevents submission: ${tagName}`);
}
}
export class MaintenancePopup extends BaseComponent {
/** @type {HTMLElement} */
@@ -11,6 +21,9 @@ export class MaintenancePopup extends BaseComponent {
/** @type {HTMLElement[]} */
#tagsList = [];
/** @type {Map<string, HTMLElement>} */
#suggestedInvalidTags = new Map();
/** @type {MaintenanceProfile|null} */
#activeProfile = null;
@@ -89,11 +102,16 @@ export class MaintenancePopup extends BaseComponent {
/** @type {string[]} */
const activeProfileTagsList = this.#activeProfile?.settings.tags || [];
for (let tagElement of this.#tagsList) {
for (const tagElement of this.#tagsList) {
tagElement.remove();
}
for (const tagElement of this.#suggestedInvalidTags.values()) {
tagElement.remove();
}
this.#tagsList = new Array(activeProfileTagsList.length);
this.#suggestedInvalidTags.clear();
const currentPostTags = this.#mediaBoxTools.mediaBox.tagsAndAliases;
@@ -109,6 +127,12 @@ export class MaintenancePopup extends BaseComponent {
tagElement.classList.toggle('is-present', isPresent);
tagElement.classList.toggle('is-missing', !isPresent);
tagElement.classList.toggle('is-aliased', isPresent && currentPostTags.get(tagName) !== tagName);
// Just to prevent duplication, we need to include this tag to the map of suggested invalid tags
if (tagsBlacklist.includes(tagName)) {
MaintenancePopup.#markTagAsInvalid(tagElement);
this.#suggestedInvalidTags.set(tagName, tagElement);
}
});
}
@@ -188,6 +212,8 @@ export class MaintenancePopup extends BaseComponent {
let maybeTagsAndAliasesAfterUpdate;
const shouldAutoRemove = await MaintenancePopup.#maintenanceSettings.resolveStripBlacklistedTags();
try {
maybeTagsAndAliasesAfterUpdate = await MaintenancePopup.#scrapedAPI.updateImageTags(
this.#mediaBoxTools.mediaBox.imageId,
@@ -200,11 +226,27 @@ export class MaintenancePopup extends BaseComponent {
tagsList.add(tagName);
}
if (shouldAutoRemove) {
for (let tagName of tagsBlacklist) {
tagsList.delete(tagName);
}
} else {
for (let tagName of tagsList) {
if (tagsBlacklist.includes(tagName)) {
throw new BlackListedTagsEncounteredError(tagName);
}
}
}
return tagsList;
}
);
} catch (e) {
console.warn('Tags submission failed:', e);
if (e instanceof BlackListedTagsEncounteredError) {
this.#revealInvalidTags();
} else {
console.warn('Tags submission failed:', e);
}
MaintenancePopup.#notifyAboutPendingSubmission(false);
this.emit('maintenance-state-change', 'failed');
@@ -228,6 +270,36 @@ export class MaintenancePopup extends BaseComponent {
this.#isSubmitting = false;
}
#revealInvalidTags() {
const tagsAndAliases = this.#mediaBoxTools.mediaBox.tagsAndAliases;
if (!tagsAndAliases) {
return;
}
const firstTagInList = this.#tagsList[0];
for (let tagName of tagsBlacklist) {
if (tagsAndAliases.has(tagName)) {
if (this.#suggestedInvalidTags.has(tagName)) {
continue;
}
const tagElement = MaintenancePopup.#buildTagElement(tagName);
MaintenancePopup.#markTagAsInvalid(tagElement);
tagElement.classList.add('is-present');
this.#suggestedInvalidTags.set(tagName, tagElement);
if (firstTagInList && firstTagInList.isConnected) {
this.#tagsListElement.insertBefore(tagElement, firstTagInList);
} else {
this.#tagsListElement.appendChild(tagElement);
}
}
}
}
/**
* @return {boolean}
*/
@@ -248,6 +320,15 @@ export class MaintenancePopup extends BaseComponent {
return tagElement;
}
/**
* Marks the tag with red color.
* @param {HTMLElement} tagElement Element to mark.
*/
static #markTagAsInvalid(tagElement) {
tagElement.dataset.tagCategory = 'error';
tagElement.setAttribute('data-tag-category', 'error');
}
/**
* Controller with maintenance settings.
* @type {MaintenanceSettings}

View File

@@ -3,6 +3,7 @@ import CacheableSettings from "$lib/extension/base/CacheableSettings.ts";
interface MaintenanceSettingsFields {
activeProfile: string | null;
stripBlacklistedTags: boolean;
}
export default class MaintenanceSettings extends CacheableSettings<MaintenanceSettingsFields> {
@@ -27,6 +28,10 @@ export default class MaintenanceSettings extends CacheableSettings<MaintenanceSe
.find(profile => profile.id === resolvedProfileId) || null;
}
async resolveStripBlacklistedTags() {
return this._resolveSetting('stripBlacklistedTags', false);
}
/**
* Set the active maintenance profile.
*
@@ -36,4 +41,8 @@ export default class MaintenanceSettings extends CacheableSettings<MaintenanceSe
async setActiveProfileId(profileId: string | null): Promise<void> {
await this._writeSetting("activeProfile", profileId);
}
async setStripBlacklistedTags(isEnabled: boolean) {
await this._writeSetting('stripBlacklistedTags', isEnabled);
}
}

View File

@@ -6,6 +6,7 @@
<Menu>
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
<hr>
<MenuItem href="/preferences/tags">Tagging</MenuItem>
<MenuItem href="/preferences/search">Search</MenuItem>
<MenuItem href="/preferences/misc">Misc & Tools</MenuItem>
<hr>

View File

@@ -0,0 +1,20 @@
<script>
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { stripBlacklistedTagsEnabled } from "$stores/maintenance-preferences.ts";
</script>
<Menu>
<MenuItem icon="arrow-left" href="/preferences">Back</MenuItem>
<hr>
</Menu>
<FormContainer>
<FormControl>
<CheckboxField bind:checked={$stripBlacklistedTagsEnabled}>
Automatically remove black-listed tags from the images
</CheckboxField>
</FormControl>
</FormContainer>

View File

@@ -0,0 +1,18 @@
import {writable} from "svelte/store";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts";
export const stripBlacklistedTagsEnabled = writable(true);
const maintenanceSettings = new MaintenanceSettings();
Promise
.all([
maintenanceSettings.resolveStripBlacklistedTags().then(v => stripBlacklistedTagsEnabled.set(v ?? true))
])
.then(() => {
maintenanceSettings.subscribe(settings => {
stripBlacklistedTagsEnabled.set(typeof settings.stripBlacklistedTags === 'boolean' ? settings.stripBlacklistedTags : true);
});
stripBlacklistedTagsEnabled.subscribe(v => maintenanceSettings.setStripBlacklistedTags(v));
});

View File

@@ -25,6 +25,27 @@ $tag-background: #1b3c21;
$tag-count-background: #2d6236;
$tag-text: #4aa158;
$tag-rating-text: #418dd9;
$tag-rating-background: darken($tag-rating-text, 35%);
$tag-spoiler-text: #d49b39;
$tag-spoiler-background: darken($tag-spoiler-text, 34%);
$tag-origin-text: #6f66d6;
$tag-origin-background: darken($tag-origin-text, 40%);
$tag-oc-text: #b157b7;
$tag-oc-background: darken($tag-oc-text, 33%);
$tag-error-text: #d45460;
$tag-error-background: desaturate(darken($tag-error-text, 38%), 6%);
$tag-character-text: #4aaabf;
$tag-character-background: darken($tag-character-text, 33%);
$tag-content-official-text: #b9b541;
$tag-content-official-background: desaturate(darken($tag-content-official-text, 29%),2%);
$tag-content-fanmade-text: #cc8eb5;
$tag-content-fanmade-background: darken($tag-content-fanmade-text, 40%);
$tag-species-text: #b16b50;
$tag-species-background: darken($tag-species-text, 35%);
$tag-body-type-text: #b8b8b8;
$tag-body-type-background: desaturate(darken($tag-body-type-text, 35%), 10%);
$input-background: #26232d;
$input-border: #5c5a61;

View File

@@ -70,6 +70,11 @@
color: colors.$tag-background;
}
&[data-tag-category=error]:hover {
background: colors.$tag-error-text;
color: colors.$tag-error-background;
}
&.is-missing:not(.is-added),
&.is-present.is-removed {
opacity: 0.5;