// ==UserScript==
// @name ValidateTagInput
// @namespace https://github.com/BrokenEagle/JavaScripts
// @version 29.7
// @description Validates tag add/remove inputs on a post edit or upload, plus several other post validations.
// @source https://danbooru.donmai.us/users/23799
// @author BrokenEagle
// @match *://*.donmai.us/
// @match *://*.donmai.us/posts*
// @match *://*.donmai.us/uploads/*
// @match *://*.donmai.us/settings
// @exclude /^https?://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @grant none
// @run-at document-end
// @downloadURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/ValidateTagInput.user.js
// @updateURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/ValidateTagInput.user.js
// @require https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/module.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/debug.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/utility.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/validate.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/storage.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/concurrency.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/statistics.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/network.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/danbooru.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/load.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20240223-menu/lib/menu.js
// ==/UserScript==
/* global JSPLib $ */
/****Global variables****/
//Library constants
////NONE
//Exterior script variables
const DANBOORU_TOPIC_ID = '14474';
//Variables for load.js
const PROGRAM_LOAD_REQUIRED_VARIABLES = ['window.jQuery', 'window.Danbooru'];
const PROGRAM_LOAD_OPTIONAL_SELECTORS = ['#c-posts #a-show', '#c-posts #a-index', '#c-uploads #a-show', '#c-upload-media-assets #a-show', '#c-users #a-edit'];
//Program name constants
const PROGRAM_SHORTCUT = 'vti';
const PROGRAM_CLICK = 'click.vti';
const PROGRAM_NAME = 'ValidateTagInput';
//Program data constants
const PROGRAM_DATA_REGEX = /^(ti|ta|are)-/; //Regex that matches the prefix of all program cache data
const PROGRAM_DATA_KEY = {
tag_alias: 'ta',
tag_implication: 'ti',
artist_entry: 'are'
};
//Main program variable
const VTI = {};
//Main settings
const SETTINGS_CONFIG = {
alias_check_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Checks and removes aliased tags from tag add validation."
},
implication_check_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Turns off querying implications for tag remove validation."
},
upload_check_enabled: {
reset: false,
validate: JSPLib.validate.isBoolean,
hint: "Performs the same rating and source checks that Danbooru does."
},
approval_check_enabled: {
reset: false,
validate: JSPLib.validate.isBoolean,
hint: "Confirms sending an upload for approval (Contributors only)."
},
artist_check_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Does a check for any artist tags or artist entries."
},
copyright_check_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: 'Checks for the existence of any copyright tag or the copyright request tag.'
},
general_check_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Performs a general tag count with up to three warning thresholds."
},
general_minimum_threshold: {
reset: 10,
parse: parseInt,
validate: (data) => (Number.isInteger(data) && data > 0),
hint: "The bare minimum number of general tags."
},
general_low_threshold: {
reset: 20,
parse: parseInt,
validate: (data) => (Number.isInteger(data) && data >= 0),
hint: "Threshold for a low amount of general tags. Enter 0 to disable this threshold."
},
general_moderate_threshold: {
reset: 30,
parse: parseInt,
validate: (data) => (Number.isInteger(data) && data >= 0),
hint: "Threshold for a moderate amount of general tags. Enter 0 to disable this threshold."
},
single_session_warning: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Pre-edit warnings will only appear once per post per tab session."
}
};
const ALL_SOURCE_TYPES = ['indexed_db', 'local_storage'];
const ALL_DATA_TYPES = ['tag_alias', 'tag_implication', 'artist_entry', 'custom'];
const CONTROL_CONFIG = {
cache_info: {
value: "Click to populate",
hint: "Calculates the cache usage of the program and compares it to the total usage.",
},
purge_cache: {
display: `Purge cache (... )`,
value: "Click to purge",
hint: `Dumps all of the cached data related to ${PROGRAM_NAME}.`,
},
data_source: {
allitems: ALL_SOURCE_TYPES,
value: 'indexed_db',
hint: "Indexed DB is Cache Data and Local Storage is Program Data .",
},
data_type: {
allitems: ALL_DATA_TYPES,
value: 'tag',
hint: "Select type of data. Use Custom for querying by keyname.",
},
raw_data: {
value: false,
hint: "Select to import/export all program data",
},
data_name: {
value: "",
buttons: ['get', 'save', 'delete', 'list', 'refresh'],
hint: "Click Get to see the data, Save to edit it, and Delete to remove it.List shows keys in their raw format, and Refresh checks the keys again.",
},
};
const MENU_CONFIG = {
topic_id: DANBOORU_TOPIC_ID,
settings: [{
name: 'general',
}, {
name: 'pre-edit',
message: "These settings affect validations when a post page is initially loaded.",
}, {
name: 'post-edit',
message: "These settings affect validations when submitting a post edit.",
}],
controls: [],
};
// Default values
const DEFAULT_VALUES = {
aliastags: [],
seenlist: [],
implicationdict: {},
implications_promise: null,
validate_lines: [],
is_check_ready: true,
is_validate_ready: true,
};
//CSS constants
const PROGRAM_CSS = `
#validation-input > label {
font-weight: bold;
}
#validation-input > * {
margin: 5px;
display: block;
}
#check-tags {
width: 7em;
margin-right: 2em;
}`;
//HTML constants
const SUBMIT_BUTTON = `
`;
const INPUT_VALIDATOR = `
Skip Validation
`;
const WARNING_MESSAGES = `
`;
const HOW_TO_TAG = `Read howto:tag for how to tag.`;
const PREEDIT_SETTINGS_DETAILS = `
Artist check enabled:
General check enabled:
The only difference between the thresholds is in the warning message given.
`;
const POSTEDIT_SETTINGS_DETAILS = `
Implications check enabled:
Turning this off effectively turns off tag remove validation.
Upload check enabled:
The main benefit is it moves the warning message closer to the submit button.
I.e in the same location as the other ${PROGRAM_NAME} warning messages.
`;
const CACHE_DATA_DETAILS = `
Tag aliases (ta): Used to determine if an added tag is bad or an alias.
Tag implications (ti): Used to determine which tag removes are bad.
Artist entry (are): Created if an artist entry exists.
`;
const PROGRAM_DATA_DETAILS = `
All timestamps are in milliseconds since the epoch (Epoch converter ).
General data
prune-expires: When the program will next check for cache data that has expired.
user-settings: All configurable settings.
`;
//Wait time for quick edit box
// 1. Let box close before reenabling the submit button
// 2. Let box open before querying the implications
const QUICKEDIT_WAIT_TIME = 1000;
const UPLOAD_SUBMIT_WAIT_TIME = JSPLib.utility.one_second * 5;
//Polling interval for checking program status
const TIMER_POLL_INTERVAL = 100;
//Expiration time is one month
const PRUNE_EXPIRES = JSPLib.utility.one_day;
const RELATION_EXPIRATION = JSPLib.utility.one_month;
const ARTIST_EXPIRATION = JSPLib.utility.one_month;
//Tag regexes
const METATAGS_REGEX = /^(?:rating|-?parent|source|-?locked|-?pool|newpool|-?fav|child|-?favgroup|upvote|downvote):/i;
const TYPETAGS_REGEX = /^-?(?:general|gen|artist|art|copyright|copy|co|character|char|ch|meta):/i;
const NEGATIVE_REGEX = /^-/;
const STRIPTYPE_REGEX = /^(-?)(?:general:|gen:|artist:|art:|copyright:|copy:|co:|character:|char:|ch:|meta:)?(.*)/i;
const COSPLAY_REGEX = /^(.+)_\(cosplay\)$/;
const SCHOOL_REGEX = /^(.+)_school_uniform$/;
//Network constants
const QUERY_LIMIT = 100;
//Other constants
const TAG_FIELDS = "id,name";
const RELATION_FIELDS = "id,antecedent_name,consequent_name";
//Validate constants
const RELATION_CONSTRAINTS = {
entry: JSPLib.validate.arrayentry_constraints(),
value: JSPLib.validate.basic_stringonly_validator
};
const ARTIST_CONSTRAINTS = {
expires: JSPLib.validate.expires_constraints,
value: JSPLib.validate.inclusion_constraints([true])
};
/****Functions****/
//Validate functions
function ValidateEntry(key, entry) {
if (!JSPLib.validate.validateIsHash(key, entry)) {
return false;
}
if (key.match(/^(ti|ta)-/)) {
return ValidateRelationEntry(key, entry);
} if (key.match(/^are-/)) {
return JSPLib.validate.validateHashEntries(key, entry, ARTIST_CONSTRAINTS);
}
this.debug('log', "Bad key!");
return false;
}
function ValidateRelationEntry(key, entry) {
if (!JSPLib.validate.validateHashEntries(key, entry, RELATION_CONSTRAINTS.entry)) {
return false;
}
return JSPLib.validate.validateArrayValues(key + '.value', entry.value, RELATION_CONSTRAINTS.value);
}
function ValidateProgramData(key, entry) {
var checkerror = [];
switch (key) {
case 'vti-user-settings':
checkerror = JSPLib.menu.validateUserSettings(entry, SETTINGS_CONFIG);
break;
case 'vti-prune-expires':
if (!Number.isInteger(entry)) {
checkerror = ["Value is not an integer."];
}
break;
default:
checkerror = ["Not a valid program data key."];
}
if (checkerror.length) {
JSPLib.validate.outputValidateError(key, checkerror);
return false;
}
return true;
}
//Library functions
////NONE
//Helper functions
function GetTagList() {
return JSPLib.utility.filterEmpty(StripQuoteSourceMetatag($("#upload_tag_string,#post_tag_string").val() || "").split(/[\s\n]+/).map((tag) => tag.toLowerCase()));
}
function StripQuoteSourceMetatag(str) {
return str.replace(/source:"[^"]+"\s?/g, '');
}
function GetNegativetags(array) {
return JSPLib.utility.filterRegex(array, NEGATIVE_REGEX, false).map((value) => value.substring(1));
}
function TransformTypetags(array) {
return array.map((value) => value.match(STRIPTYPE_REGEX).splice(1).join(''));
}
function GetCurrentTags() {
return JSPLib.utility.filterRegex(JSPLib.utility.filterRegex(GetTagList(), METATAGS_REGEX, true), TYPETAGS_REGEX, true);
}
function GetAutoImplications() {
VTI.preedittags.forEach((tag) => {
let match = tag.match(COSPLAY_REGEX);
if (match) {
let base_tag = match[1];
this.debug('log', "Found:", tag, '->', 'cosplay');
this.debug('log', "Found:", tag, '->', base_tag);
VTI.implicationdict.cosplay = VTI.implicationdict.cosplay || [];
VTI.implicationdict.cosplay.push(tag);
VTI.implicationdict[base_tag] = VTI.implicationdict[base_tag] || [];
VTI.implicationdict[base_tag].push(tag);
}
match = tag.match(SCHOOL_REGEX);
if (match) {
this.debug('log', "Found:", tag, '->', 'school_uniform');
VTI.implicationdict.school_uniform = VTI.implicationdict.school_uniform || [];
VTI.implicationdict.school_uniform.push(tag);
}
});
}
function GetAllRelations(tag, implicationdict) {
var tmp = [];
if (tag in implicationdict) {
for(let i = 0;i < implicationdict[tag].length;i++) {
tmp.push(implicationdict[tag][i]);
let tmp2 = GetAllRelations(implicationdict[tag][i], implicationdict);
tmp = tmp.concat(tmp2);
}
return tmp;
}
return [];
}
function IsSkipValidate() {
return $("#skip-validate-tags")[0].checked;
}
function DisableUI(type) {
$("#validate-tags")[0].setAttribute('disabled', 'true');
$("#check-tags")[0].setAttribute('disabled', 'true');
if (type === "submit") {
$("#validate-tags")[0].setAttribute('value', 'Submitting...');
} else if (type === "check") {
$("#check-tags")[0].setAttribute('value', 'Checking...');
}
}
function EnableUI(type) {
$("#validate-tags")[0].removeAttribute('disabled');
$("#check-tags")[0].removeAttribute('disabled');
if (type === "submit") {
$("#validate-tags")[0].setAttribute('value', 'Submit');
} else if (type === "check") {
$("#check-tags")[0].setAttribute('value', 'Check');
}
}
//Network functions
//Queries aliases of added tags... can be called multiple times
async function QueryTagAliases(taglist) {
let unseen_tags = JSPLib.utility.arrayDifference(taglist, VTI.seenlist);
let [cached_aliases, uncached_aliases] = await JSPLib.storage.batchStorageCheck(unseen_tags, ValidateEntry, RELATION_EXPIRATION, 'ta');
this.debug('log', "Cached aliases:", cached_aliases);
this.debug('log', "Uncached aliases:", uncached_aliases);
if (uncached_aliases.length) {
let options = {url_addons: {search: {antecedent_name_space: uncached_aliases.join(' '), status: 'active'}, only: RELATION_FIELDS}, long_format: true};
let all_aliases = await JSPLib.danbooru.getAllItems('tag_aliases', QUERY_LIMIT, options);
let found_aliases = [];
all_aliases.forEach((alias) => {
found_aliases.push(alias.antecedent_name);
JSPLib.storage.saveData('ta-' + alias.antecedent_name, {value: [alias.consequent_name], expires: JSPLib.utility.getExpires(RELATION_EXPIRATION)});
});
let unfound_aliases = JSPLib.utility.arrayDifference(uncached_aliases, found_aliases);
unfound_aliases.forEach((tag) => {
JSPLib.storage.saveData('ta-' + tag, {value: [], expires: JSPLib.utility.getExpires(RELATION_EXPIRATION)});
});
VTI.aliastags = JSPLib.utility.concat(VTI.aliastags, found_aliases);
this.debug('log', "Found aliases:", found_aliases);
this.debug('log', "Unfound aliases:", unfound_aliases);
}
cached_aliases.forEach((tag) => {
let data = JSPLib.storage.getIndexedSessionData('ta-' + tag, {value: []}).value;
if (data.length) {
VTI.aliastags.push(tag);
}
});
VTI.seenlist = JSPLib.utility.concat(VTI.seenlist, unseen_tags);
this.debug('log', "Aliases:", VTI.aliastags);
}
//Queries implications of preexisting tags... called once per image
async function QueryTagImplications(taglist) {
let [cached_implications, uncached_implications] = await JSPLib.storage.batchStorageCheck(taglist, ValidateEntry, RELATION_EXPIRATION, 'ti');
this.debug('log', "Cached implications:", cached_implications);
this.debug('log', "Uncached implications:", uncached_implications);
if (uncached_implications.length) {
let options = {url_addons: {search: {consequent_name_space: uncached_implications.join(' '), status: 'active'}, only: RELATION_FIELDS}, long_format: true};
let all_implications = await JSPLib.danbooru.getAllItems('tag_implications', QUERY_LIMIT, options);
all_implications.forEach((implication) => {
let tag = implication.consequent_name;
VTI.implicationdict[tag] = VTI.implicationdict[tag] || [];
VTI.implicationdict[tag].push(implication.antecedent_name);
});
for (let tag in VTI.implicationdict) {
JSPLib.storage.saveData('ti-' + tag, {value: VTI.implicationdict[tag], expires: JSPLib.utility.getExpires(RELATION_EXPIRATION)});
}
let found_implications = Object.keys(VTI.implicationdict);
let unfound_implications = JSPLib.utility.arrayDifference(uncached_implications, found_implications);
unfound_implications.forEach((tag) => {
JSPLib.storage.saveData('ti-' + tag, {value: [], expires: JSPLib.utility.getExpires(RELATION_EXPIRATION)});
});
this.debug('log', "Found implications:", found_implications);
this.debug('log', "Unfound implications:", unfound_implications);
}
cached_implications.forEach((tag) => {
let data = JSPLib.storage.getIndexedSessionData('ti-' + tag).value;
if (data.length) {
VTI.implicationdict[tag] = data;
}
});
this.debug('log', "Implications:", VTI.implicationdict);
}
//Event handlers
function PostModeMenu(event) {
let s = $("#mode-box select").val();
if (s === "edit") {
$("#validation-input,#warning-bad-upload,#warning-new-tags,#warning-bad-removes").hide();
let post_id = $(event.target).closest("article").data("id");
let $post = $("#post_" + post_id);
VTI.preedittags = $post.data("tags").split(' ');
this.debug('log', "Preedit tags:", VTI.preedittags);
//Wait until the edit box loads before querying implications
if (VTI.user_settings.implication_check_enabled) {
setTimeout(() => {
VTI.implications_promise = QueryTagImplications(VTI.preedittags);
VTI.implications_promise.then(() => {
this.debug('log', "Adding auto implications");
GetAutoImplications();
});
}, QUICKEDIT_WAIT_TIME);
}
event.preventDefault();
}
}
async function CheckTags() {
//Prevent code from being reentrant until finished processing
if (VTI.is_check_ready) {
VTI.is_check_ready = false;
DisableUI("check");
let statuses = (await Promise.all([ValidateTagAdds(), ValidateTagRemoves(), ValidateUpload()]));
if (statuses.every((item) => item)) {
JSPLib.notice.notice("Tags good to submit!");
} else {
JSPLib.notice.error("Tag validation failed!");
}
EnableUI("check");
VTI.is_check_ready = true;
}
}
async function ValidateTags() {
//Prevent code from being reentrant until finished processing
if (VTI.is_validate_ready) {
VTI.is_validate_ready = false;
DisableUI("submit");
let statuses = await Promise.all([ValidateTagAdds(), ValidateTagRemoves(), ValidateUpload()]);
if (statuses.every((item) => item)) {
if (VTI.user_settings.approval_check_enabled && $('#post_is_pending').prop('checked') && !confirm("Submit upload for approval?")) {
VTI.is_validate_ready = true;
EnableUI("submit");
return;
}
this.debug('log', "Submit request!");
$("#form [name=commit],#quick-edit-form [name=commit]").click();
if ((VTI.controller === 'uploads' && VTI.action === 'new') || (VTI.controller === 'posts' && VTI.controller === 'show')) {
this.debug('log', "Disabling return key!");
$("#upload_tag_string,#post_tag_string").off("keydown.vti");
}
if (VTI.is_upload) {
setTimeout(() => {
EnableUI("submit");
RebindHotkey();
VTI.is_validate_ready = true;
JSPLib.notice.error('Submission timed out: check client form for errors. (navigate )');
}, UPLOAD_SUBMIT_WAIT_TIME);
} else if (VTI.controller === 'posts' && VTI.action === 'index') {
//Wait until the edit box closes to reenable the submit button click
setTimeout(() => {
this.debug('log', "Ready for next edit!");
EnableUI("submit");
$("#skip-validate-tags")[0].checked = false;
VTI.is_validate_ready = true;
}, QUICKEDIT_WAIT_TIME);
}
} else {
this.debug('log', "Validation failed!");
EnableUI("submit");
VTI.is_validate_ready = true;
}
}
}
//Timer/callback functions
function RebindHotkey() {
JSPLib.utility.recheckTimer({
check: () => JSPLib.utility.isNamespaceBound("#upload_tag_string,#post_tag_string", 'keydown', 'danbooru.submit_form'),
exec: () => {
$("#upload_tag_string,#post_tag_string").off("keydown.danbooru.submit").off("keydown.danbooru.submit_form").on("keydown.vti", null, "ctrl+return", (event) => {
$("#validate-tags").click();
event.preventDefault();
});
}
}, TIMER_POLL_INTERVAL);
}
//Main execution functions
async function ValidateTagAdds() {
let postedittags = GetCurrentTags();
let positivetags = JSPLib.utility.filterRegex(postedittags, NEGATIVE_REGEX, true);
let useraddtags = JSPLib.utility.arrayDifference(positivetags, VTI.preedittags);
VTI.addedtags = JSPLib.utility.arrayDifference(useraddtags, GetNegativetags(postedittags));
this.debug('log', "Added tags:", VTI.addedtags);
if ((VTI.addedtags.length === 0) || IsSkipValidate()) {
this.debug('log', "Skipping!");
$("#warning-new-tags").hide();
return true;
}
let options = {url_addons: {search: {name_space: VTI.addedtags.join(' '), hide_empty: 'yes'}, only: TAG_FIELDS}, long_format: true};
let all_aliases = await JSPLib.danbooru.getAllItems('tags', QUERY_LIMIT, options);
VTI.checktags = JSPLib.utility.getObjectAttributes(all_aliases, 'name');
let nonexisttags = JSPLib.utility.arrayDifference(VTI.addedtags, VTI.checktags);
if (VTI.user_settings.alias_check_enabled) {
await QueryTagAliases(nonexisttags);
nonexisttags = JSPLib.utility.arrayDifference(nonexisttags, VTI.aliastags);
}
if (nonexisttags.length > 0) {
this.debug('log', "Nonexistant tags!");
nonexisttags.forEach((tag, i) => {this.debug('log', i, tag);});
$("#validation-input").show();
$("#warning-new-tags").show();
let taglist = nonexisttags.join(', ');
$("#warning-new-tags")[0].innerHTML = 'Warning : The following new tags will be created: ' + taglist;
return false;
}
this.debug('log', "Free and clear to submit!");
$("#warning-new-tags").hide();
return true;
}
async function ValidateTagRemoves() {
if (!VTI.user_settings.implication_check_enabled || IsSkipValidate()) {
this.debug('log', "Skipping!");
$("#warning-bad-removes").hide();
return true;
}
await VTI.implications_promise;
let postedittags = TransformTypetags(GetCurrentTags());
let deletedtags = JSPLib.utility.arrayDifference(VTI.preedittags, postedittags);
let negatedtags = JSPLib.utility.arrayIntersection(GetNegativetags(postedittags), postedittags);
let removedtags = deletedtags.concat(negatedtags);
let finaltags = JSPLib.utility.arrayDifference(postedittags, removedtags);
this.debug('log', "Final tags:", finaltags);
this.debug('log', "Removed tags:", deletedtags, negatedtags);
let allrelations = [];
removedtags.forEach((tag) => {
let badremoves = JSPLib.utility.arrayIntersection(GetAllRelations(tag, VTI.implicationdict), finaltags);
if (badremoves.length) {
allrelations.push(badremoves.toString() + ' -> ' + tag);
}
});
if (allrelations.length) {
JSPLib.debug.debugExecute(() => {
this.debug('log', "Badremove tags!");
allrelations.forEach((relation, i) => {this.debug('log', i, relation);});
});
$("#validation-input").show();
$("#warning-bad-removes").show();
let removelist = allrelations.join(' ');
$("#warning-bad-removes")[0].innerHTML = 'Notice : The following implication relations prevent certain tag removes: ' + removelist;
return false;
}
this.debug('log', "Free and clear to submit!");
$("#warning-bad-removes").hide();
return true;
}
function ValidateUpload() {
if (!VTI.user_settings.upload_check_enabled || !VTI.is_upload || IsSkipValidate()) {
this.debug('log', "Skipping!");
$("#warning-bad-upload").hide();
return true;
}
let errormessages = [];
let ratingtag = Boolean(JSPLib.utility.filterRegex(GetTagList(), /^rating:\w/).length);
let ratingradio = $(".post_rating input").toArray().some((input) => input.checked);
if (!ratingtag && !ratingradio) {
errormessages.push("Must specify a rating.");
}
if (errormessages.length) {
this.debug('log', "Errors: " + errormessages.join(' '));
$("#validation-input").show();
$("#warning-bad-upload").show();
$("#warning-bad-upload")[0].innerHTML = 'Warning : ' + errormessages.join(' ');
return false;
}
this.debug('log', "Free and clear to submit!");
$("#warning-bad-upload").hide();
return true;
}
async function ValidateArtist() {
let source_url = $("#post_source").val();
let artist_names = $(".artist-tag-list .tag-type-1 .wiki-link").map((i, entry) => decodeURIComponent(JSPLib.utility.parseParams(entry.search.slice(1)).name)).toArray();
if (artist_names.length === 0 && !VTI.preedittags.includes('official_art')) {
//Validate no artist tag
let option_html = "";
if (!source_url.match(/https?:\/\//)) {
this.debug('log', "Not a URL.");
return;
}
let source_resp = await JSPLib.danbooru.submitRequest('source', {url: source_url}, {default_val: {artist: {name: null}}});
if (source_resp.artist.name === null) {
this.debug('log', "Not a first-party source.");
return;
}
if (source_resp.artists.length) {
let artist_list = source_resp.artists.map((artist) => `${artist.name} `);
let artist_html = `There is an available artist tag for this post [${artist_list.join(', ')}]. Open the edit menu and consider adding it.`;
VTI.validate_lines.push(artist_html);
} else {
if (!VTI.preedittags.includes('artist_request')) {
option_html = ` ...or, consider adding at least artist request or official art as applicable.`;
}
let new_artist_addons = $.param({artist: {source: source_url}});
let artist_html = `Artist tag is required. Create new artist entry . Ask on the forum if you need naming help.`;
VTI.validate_lines = VTI.validate_lines.concat([artist_html + option_html]);
}
} else {
//Validate artists have entry
let [, uncached_artists] = await JSPLib.storage.batchStorageCheck(artist_names, ValidateEntry, ARTIST_EXPIRATION, 'are');
if (uncached_artists.length === 0) {
this.debug('log', "No missing artists. [cache hit]");
return;
}
let tag_resp = await JSPLib.danbooru.submitRequest('tags', {search: {name_space: uncached_artists.join(' '), has_artist: true}, only: TAG_FIELDS}, {default_val: []});
tag_resp.forEach((entry) => {
JSPLib.storage.saveData('are-' + entry.name, {value: true, expires: JSPLib.utility.getExpires(ARTIST_EXPIRATION)});
});
let found_artists = JSPLib.utility.getObjectAttributes(tag_resp, 'name');
let missing_artists = JSPLib.utility.arrayDifference(uncached_artists, found_artists);
if (missing_artists.length === 0) {
this.debug('log', "No missing artists. [cache miss]");
return;
}
let artist_lines = missing_artists.map((artist) => {
let new_artist_addons = $.param({artist: {source: source_url, name: artist}});
return `
Artist ${artist} requires an artist entry.
Create new artist entry `;
});
VTI.validate_lines = VTI.validate_lines.concat(artist_lines);
}
JSPLib.notice.notice(VTI.validate_lines.join(' '), true);
}
function ValidateCopyright() {
let copyright_names_length = $(".copyright-tag-list .tag-type-3 .wiki-link").length;
if (copyright_names_length) {
this.debug('log', "Has a copyright.");
return;
} if (VTI.preedittags.includes('copyright_request')) {
this.debug('log', "Has copyright request.");
return;
}
let copyright_html = `Copyright tag is required. Consider adding copyright request or original .`;
VTI.validate_lines.push(copyright_html);
JSPLib.notice.notice(VTI.validate_lines.join(' '), true);
}
function ValidateGeneral() {
let general_tags_length = $(".general-tag-list .tag-type-0 .wiki-link").length;
if (general_tags_length < VTI.user_settings.general_minimum_threshold) {
VTI.validate_lines.push("Posts must have at least 10 general tags. Please add some more tags. " + HOW_TO_TAG);
} else if (VTI.user_settings.general_low_threshold && general_tags_length < VTI.user_settings.general_low_threshold) {
VTI.validate_lines.push("The post has a low amount of general tags. Consider adding more. " + HOW_TO_TAG);
} else if (VTI.user_settings.general_moderate_threshold && general_tags_length < VTI.user_settings.general_moderate_threshold) {
VTI.validate_lines.push("The post has a moderate amount of general tags, but could potentially need more. " + HOW_TO_TAG);
} else {
this.debug('log', "Has enough tags.");
return;
}
JSPLib.notice.notice(VTI.validate_lines.join(' '), true);
}
function CleanupTasks() {
JSPLib.storage.pruneEntries(PROGRAM_SHORTCUT, PROGRAM_DATA_REGEX, PRUNE_EXPIRES);
}
//Settings functions
function InitializeProgramValues() {
Object.assign(VTI, {
is_upload: (VTI.action === 'show' && (VTI.controller === 'uploads' || VTI.controller === 'upload-media-assets')),
is_post_show: (VTI.controller === 'posts' && VTI.action === 'show'),
is_post_index: (VTI.controller === 'posts' && VTI.action === 'index'),
was_upload: JSPLib.storage.getStorageData('vti-was-upload', sessionStorage, false),
});
Object.assign(VTI, {
preedittags: (VTI.is_post_show ? $(".image-container").data('tags').split(' ') : [] ),
});
if (VTI.is_upload) {
JSPLib.storage.setStorageData('vti-was-upload', true, sessionStorage);
} else {
JSPLib.storage.setStorageData('vti-was-upload', false, sessionStorage);
}
return true;
}
function RenderSettingsMenu() {
$('#validate-tag-input').append(JSPLib.menu.renderMenuFramework(MENU_CONFIG));
$("#vti-general-settings").append(JSPLib.menu.renderDomainSelectors());
$('#vti-pre-edit-settings-message').append(JSPLib.menu.renderExpandable("Additional setting details", PREEDIT_SETTINGS_DETAILS));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderCheckbox('single_session_warning'));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderCheckbox('artist_check_enabled'));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderCheckbox('copyright_check_enabled'));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderCheckbox('general_check_enabled'));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderTextinput('general_minimum_threshold', 10));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderTextinput('general_low_threshold', 10));
$("#vti-pre-edit-settings").append(JSPLib.menu.renderTextinput('general_moderate_threshold', 10));
$('#vti-post-edit-settings-message').append(JSPLib.menu.renderExpandable("Additional setting details", POSTEDIT_SETTINGS_DETAILS));
$("#vti-post-edit-settings").append(JSPLib.menu.renderCheckbox('alias_check_enabled'));
$("#vti-post-edit-settings").append(JSPLib.menu.renderCheckbox('implication_check_enabled'));
$("#vti-post-edit-settings").append(JSPLib.menu.renderCheckbox('upload_check_enabled'));
$("#vti-post-edit-settings").append(JSPLib.menu.renderCheckbox('approval_check_enabled'));
$('#vti-controls').append(JSPLib.menu.renderCacheControls());
$('#vti-cache-controls-message').append(JSPLib.menu.renderExpandable("Cache Data details", CACHE_DATA_DETAILS));
$("#vti-cache-controls").append(JSPLib.menu.renderLinkclick('cache_info', true));
$('#vti-cache-controls').append(JSPLib.menu.renderCacheInfoTable());
$("#vti-cache-controls").append(JSPLib.menu.renderLinkclick('purge_cache', true));
$('#vti-controls').append(JSPLib.menu.renderCacheEditor(true));
$('#vti-cache-editor-message').append(JSPLib.menu.renderExpandable("Program Data details", PROGRAM_DATA_DETAILS));
$("#vti-cache-editor-controls").append(JSPLib.menu.renderKeyselect('data_source', true));
$("#vti-cache-editor-controls").append(JSPLib.menu.renderDataSourceSections());
$("#vti-section-indexed-db").append(JSPLib.menu.renderKeyselect('data_type', true));
$("#vti-section-local-storage").append(JSPLib.menu.renderCheckbox('raw_data', true));
$("#vti-cache-editor-controls").append(JSPLib.menu.renderTextinput('data_name', 20, true));
JSPLib.menu.engageUI(true);
JSPLib.menu.saveUserSettingsClick();
JSPLib.menu.resetUserSettingsClick();
JSPLib.menu.cacheInfoClick();
JSPLib.menu.purgeCacheClick();
JSPLib.menu.expandableClick();
JSPLib.menu.dataSourceChange();
JSPLib.menu.rawDataChange();
JSPLib.menu.getCacheClick(ValidateProgramData);
JSPLib.menu.saveCacheClick(ValidateProgramData, ValidateEntry);
JSPLib.menu.deleteCacheClick();
JSPLib.menu.listCacheClick();
JSPLib.menu.refreshCacheClick();
JSPLib.menu.cacheAutocomplete();
}
//Main program
function Main() {
this.debug('log', "Initialize start:", JSPLib.utility.getProgramTime());
const preload = {
run_on_settings: false,
default_data: DEFAULT_VALUES,
initialize_func: InitializeProgramValues,
};
if (!JSPLib.menu.preloadScript(VTI, RenderSettingsMenu, preload)) return;
$("#form [name=commit],#quick-edit-form [name=commit]").after(SUBMIT_BUTTON).hide();
if (VTI.is_post_index) {
$(".post-preview a").on(PROGRAM_CLICK, PostModeMenu);
$("#quick-edit-form").append(INPUT_VALIDATOR);
$("#quick-edit-form").after(WARNING_MESSAGES);
} else {
$("#related-tags-container").before(INPUT_VALIDATOR);
$("#related-tags-container").before(WARNING_MESSAGES);
}
if (VTI.is_post_show) {
this.debug('log', "Preedit tags:", VTI.preedittags);
if (VTI.user_settings.implication_check_enabled) {
VTI.implications_promise = QueryTagImplications(VTI.preedittags);
VTI.implications_promise.then(() => {
this.debug('log', "Adding auto implications");
GetAutoImplications();
});
}
let post_id = parseInt(JSPLib.utility.getMeta('post-id'));
let seen_post_list = JSPLib.storage.getStorageData('vti-seen-postlist', sessionStorage, []);
if (!VTI.was_upload && (!VTI.user_settings.single_session_warning || !seen_post_list.includes(post_id))) {
if (VTI.user_settings.artist_check_enabled) {
ValidateArtist();
}
if (VTI.user_settings.copyright_check_enabled) {
ValidateCopyright();
}
if (VTI.user_settings.general_check_enabled) {
ValidateGeneral();
}
} else {
this.debug('log', "Already pre-validated post.");
}
JSPLib.storage.setStorageData('vti-seen-postlist', JSPLib.utility.arrayUnique(seen_post_list.concat(post_id)), sessionStorage);
} else if (VTI.is_upload) {
let $pending_input = $('.post_is_pending').detach();
$("#check-tags").after($pending_input);
}
$("#validate-tags").on(PROGRAM_CLICK, ValidateTags);
$("#check-tags").on(PROGRAM_CLICK, CheckTags);
RebindHotkey();
JSPLib.utility.setCSSStyle(PROGRAM_CSS, 'program');
JSPLib.statistics.addPageStatistics(PROGRAM_NAME);
JSPLib.load.noncriticalTasks(CleanupTasks);
}
/****Function decoration****/
[
Main, ValidateEntry, PostModeMenu, ValidateTags, GetAutoImplications,
QueryTagAliases, QueryTagImplications, ValidateTagAdds, ValidateTagRemoves, ValidateUpload,
ValidateArtist, ValidateCopyright, ValidateGeneral,
] = JSPLib.debug.addFunctionLogs([
Main, ValidateEntry, PostModeMenu, ValidateTags, GetAutoImplications,
QueryTagAliases, QueryTagImplications, ValidateTagAdds, ValidateTagRemoves, ValidateUpload,
ValidateArtist, ValidateCopyright, ValidateGeneral,
]);
[
RenderSettingsMenu,
QueryTagAliases, QueryTagImplications, ValidateTagAdds, ValidateTagRemoves, ValidateArtist,
ValidateTags, CheckTags,
] = JSPLib.debug.addFunctionTimers([
//Sync
RenderSettingsMenu,
//Async
QueryTagAliases, QueryTagImplications, ValidateTagAdds, ValidateTagRemoves, ValidateArtist,
ValidateTags, CheckTags,
]);
/****Initialization****/
//Variables for debug.js
JSPLib.debug.debug_console = false;
JSPLib.debug.level = JSPLib.debug.INFO;
JSPLib.debug.program_shortcut = PROGRAM_SHORTCUT;
//Variables for menu.js
JSPLib.menu.program_shortcut = PROGRAM_SHORTCUT;
JSPLib.menu.program_name = PROGRAM_NAME;
JSPLib.menu.program_data = VTI;
JSPLib.menu.program_data_regex = PROGRAM_DATA_REGEX;
JSPLib.menu.program_data_key = PROGRAM_DATA_KEY;
JSPLib.menu.settings_config = SETTINGS_CONFIG;
JSPLib.menu.control_config = CONTROL_CONFIG;
//Export JSPLib
JSPLib.load.exportData(PROGRAM_NAME, VTI);
/****Execution start****/
JSPLib.load.programInitialize(Main, {program_name: PROGRAM_NAME, required_variables: PROGRAM_LOAD_REQUIRED_VARIABLES, optional_selectors: PROGRAM_LOAD_OPTIONAL_SELECTORS});