// ==UserScript== // @name DisplayPostInfo // @namespace https://github.com/BrokenEagle/JavaScripts // @version 12.9 // @description Display views, uploader, and other info to the user. // @source https://danbooru.donmai.us/users/23799 // @author BrokenEagle // @match *://*.donmai.us/ // @match *://*.donmai.us/posts* // @match *://*.donmai.us/explore/posts/* // @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/DisplayPostInfo.user.js // @updateURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/DisplayPostInfo.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://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/core.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/md5.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 $ Danbooru CryptoJS */ /****Global variables****/ //Library constants ////NONE //Exterior script variables const DANBOORU_TOPIC_ID = '15926'; //Variables for load.js const PROGRAM_LOAD_REQUIRED_VARIABLES = ['window.jQuery', 'Danbooru.PostTooltip']; const PROGRAM_LOAD_OPTIONAL_SELECTORS = ['#c-posts #a-show', '#c-posts #a-index', "#c-explore-posts #a-viewed", "#c-explore-posts #a-curated", '#c-users #a-edit']; //Program name constants const PROGRAM_SHORTCUT = 'dpi'; const PROGRAM_NAME = 'DisplayPostInfo'; //Program data constants const PROGRAM_DATA_REGEX = /^(tt|user|pv)-/; //Regex that matches the prefix of all program cache data const PROGRAM_DATA_KEY = { user_data: 'user', top_tagger: 'tt', post_views: 'pv', }; //Main program variable const DPI = {}; //Available setting values //Main settings const SETTINGS_CONFIG = { post_views_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Shows post views on the post page." }, top_tagger_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Shows top tagger on the post page." }, basic_post_tooltip: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Adds the post uploader to the basic post tooltips." }, advanced_post_tooltip: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Enables the configuration of post tooltip settings." }, post_show_delay: { reset: 500, parse: parseInt, validate: (data) => (Number.isInteger(data) && data >= 0), hint: "How long to delay showing the post tooltip (in milliseconds)." }, post_hide_delay: { reset: 125, parse: parseInt, validate: (data) => (Number.isInteger(data) && data >= 0), hint: "How long to delay hiding the post tooltip (in milliseconds)." }, post_favorites_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Adds attributes to posts that allows the user to apply their own CSS styles to them." }, post_statistics_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Shows post statistics for all of the posts on a page." }, domain_statistics_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Shows domain statistics for all of the posts on a page." }, tag_statistics_enabled: { reset: false, validate: JSPLib.validate.isBoolean, hint: "Shows the percentage of posts with the tags from the tag column." }, }; const ALL_SOURCE_TYPES = ['indexed_db', 'local_storage']; const ALL_DATA_TYPES = ['user_data', 'top_tagger', 'post_views', 'custom']; const CONTROL_CONFIG = { refresh_frequent_tags: { value: "Click to refresh", hint: "Gets the latest favorite tags from the user's profile.", }, 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: 'user_data', 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: 'information', }, { name: 'tooltip', }, { name: 'statistics', }], controls: [], }; // Default values const DEFAULT_VALUES = { user_promises: {}, }; //CSS constants const POST_INDEX_CSS = ` #dpi-post-statistics th, #dpi-domain-statistics th { text-align: right; } #dpi-post-statistics th, #dpi-post-statistics td { padding: 2px; } .dpi-domain-overflow { color: blue; } .dpi-tag-statistic { color:lightpink; } #c-posts #a-index #sidebar { width: 16em; } `; //HTML constants const POST_VIEWS_LINE = ''; const USER_NAMES_LINE = ''; const CACHE_DATA_DETAILS = ` `; const PROGRAM_DATA_DETAILS = `

All timestamps are in milliseconds since the epoch (Epoch converter).

`; const POST_INDEX_STATISTICS = `
`; const POST_STATISTICS_TABLE = `

Post statistics

Score: %SCORE_AVERAGE% ± %SCORE_DEVIATION%
Favorites: %FAVES_AVERAGE% ± %FAVES_DEVIATION%
General: %GENERAL_PERCENTAGE%%
Sensitive: %SENSITIVE_PERCENTAGE%%
Questionable: %QUESTIONABLE_PERCENTAGE%%
Explicit: %EXPLICIT_PERCENTAGE%%
Pending: %PENDING_COUNT%
Banned: %BANNED_COUNT%
Flagged: %FLAGGED_COUNT%
Deleted: %DELETED_COUNT%
`; const DOMAIN_STATISTICS_TABLE = `

Domain statistics

`; const DOMAIN_STATISTICS_ROW = ` %DOMAIN_NAME% %DOMAIN_COUNT% `; //Time constants const PRUNE_EXPIRES = JSPLib.utility.one_day; const TOP_TAGGER_EXPIRATION = JSPLib.utility.one_month; const USER_EXPIRATION = JSPLib.utility.one_month; const BAD_USER_EXPIRATION = JSPLib.utility.one_day; const MIN_VIEWS_EXPIRATION = JSPLib.utility.one_minute; const MID_VIEWS_EXPIRATION = JSPLib.utility.one_hour; const MAX_VIEWS_EXPIRATION = JSPLib.utility.one_day; //Other constants const USER_FIELDS = "id,name,level_string,can_upload_free,can_approve_posts"; const POSTVER_FIELDS = "id,version,updater_id,unchanged_tags,added_tags"; //Data inclusion lists const ALL_LEVELS = ['Member', 'Gold', 'Platinum', 'Builder', 'Moderator', 'Admin']; //Validate constants const TOP_TAGGER_CONSTRAINTS = { expires: JSPLib.validate.expires_constraints, value: JSPLib.validate.id_constraints }; const USER_CONSTRAINTS = { entry: JSPLib.validate.hashentry_constraints, value: { name: JSPLib.validate.stringonly_constraints, level: JSPLib.validate.inclusion_constraints(ALL_LEVELS), contributor: JSPLib.validate.boolean_constraints, approver: JSPLib.validate.boolean_constraints } }; const VIEW_CONSTRAINTS = { expires: JSPLib.validate.expires_constraints, value: JSPLib.validate.expires_constraints }; /****FUNCTIONS****/ //Validate functions function ValidateEntry(key, entry) { if (!JSPLib.validate.validateIsHash(key, entry)) { return false; } if (key.match(/^user-/)) { return ValidateUserEntry(key, entry); } if (key.match(/^tt-/)) { return JSPLib.validate.validateHashEntries(key, entry, TOP_TAGGER_CONSTRAINTS); } if (key.match(/^pv-/)) { return JSPLib.validate.validateHashEntries(key, entry, VIEW_CONSTRAINTS); } this.debug('log', "Bad key!"); return false; } function ValidateUserEntry(key, entry) { if (!JSPLib.validate.validateHashEntries(key, entry, USER_CONSTRAINTS.entry)) { return false; } return JSPLib.validate.validateHashEntries(key + '.value', entry.value, USER_CONSTRAINTS.value); } function ValidateProgramData(key, entry) { var checkerror = []; switch (key) { case 'dpi-user-settings': checkerror = JSPLib.menu.validateUserSettings(entry, SETTINGS_CONFIG); break; case 'dpi-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 //Auxiliary functions function BlankUser(user_id) { return { name: `user_${user_id}`, level: "Member", contributor: false, approver: false, }; } function MapUserData(user) { return { name: user.name, level: user.level_string, contributor: user.can_upload_free, approver: user.can_approve_posts }; } function RenderUsername(user_id, user_data) { let user_name = JSPLib.utility.maxLengthString(user_data.name.replace(/_/g, ' ')); let level_class = "user-" + user_data.level.toLowerCase(); let unlimited_class = (user_data.contributor ? " user-post-uploader" : ""); let approver_class = (user_data.approver ? " user-post-approver" : ""); return ``; } function PopulateUserTags(current_tags, added_tags, user_tags, version_order, updater_id) { user_tags[updater_id] = user_tags[updater_id] || []; user_tags[updater_id] = JSPLib.utility.concat(user_tags[updater_id], (added_tags)); version_order.unshift(updater_id); current_tags.tags = JSPLib.utility.arrayDifference(current_tags.tags, added_tags); } function SaveMappedListData(mapped_data, expiration) { mapped_data.forEach((user) => { let user_id = Object.keys(user)[0]; JSPLib.storage.saveData(`user-${user_id}`, {value: user[user_id], expires: JSPLib.utility.getExpires(expiration)}); }); } function LogarithmicExpiration(count, max_count, time_divisor, multiplier) { let time_exponent = Math.pow(10, (1 / time_divisor)); return Math.round(Math.log10(time_exponent + (10 - time_exponent) * (count / max_count)) * multiplier); } function PostViewsExpiration(created_timestamp) { let created_interval = Date.now() - created_timestamp; if (created_interval < JSPLib.utility.one_hour) { return MIN_VIEWS_EXPIRATION; } if (created_interval < JSPLib.utility.one_day) { let hour_interval = (created_interval / JSPLib.utility.one_hour) - 1; //Start at 0 hours and go to 23 hours let hour_slots = 23; //There are 23 hour slots between 1 hour and 24 hours let minutes_hour = 60; return LogarithmicExpiration(hour_interval, hour_slots, minutes_hour, MID_VIEWS_EXPIRATION); } if (created_interval < JSPLib.utility.one_month) { let day_interval = (created_interval / JSPLib.utility.one_day) - 1; //Start at 0 days and go to 29 days let day_slots = 29; //There are 29 days slots between 1 day and 30 days let hours_day = 24; return LogarithmicExpiration(day_interval, day_slots, hours_day, MAX_VIEWS_EXPIRATION); } return MAX_VIEWS_EXPIRATION; } //Network functions async function GetUserData(user_id) { let user_key = `user-${user_id}`; let data = await JSPLib.storage.checkLocalDB(user_key, ValidateEntry, USER_EXPIRATION); var mapped_data; if (!data) { this.debug('log', "Querying:", user_id); let user_data = await JSPLib.danbooru.submitRequest("users", {search: {id: user_id, expiry: 30}, only: USER_FIELDS}); if (user_data && user_data.length) { mapped_data = MapUserData(user_data[0]); JSPLib.storage.saveData(user_key, {value: mapped_data, expires: JSPLib.utility.getExpires(USER_EXPIRATION)}); } else { this.debug('log', "Missing user:", user_id); mapped_data = BlankUser(user_id); JSPLib.storage.saveData(user_key, {value: mapped_data, expires: JSPLib.utility.getExpires(BAD_USER_EXPIRATION)}); } } else { mapped_data = data.value; } return mapped_data; } async function GetUserListData(userid_list) { var mapped_list_data = []; let [found_users, missing_users] = await JSPLib.storage.batchStorageCheck(userid_list, ValidateEntry, USER_EXPIRATION, 'user'); if (missing_users.length) { this.debug('log', "Missing users:", missing_users); let user_list = await JSPLib.danbooru.submitRequest("users", {search: {id: missing_users.join(',')}, limit: missing_users.length, only: USER_FIELDS}); mapped_list_data = user_list.map((user) => ({[user.id]: MapUserData(user)})); SaveMappedListData(mapped_list_data, USER_EXPIRATION); if (user_list.length !== missing_users.length) { let found_users = JSPLib.utility.getObjectAttributes(user_list, 'id'); let bad_users = JSPLib.utility.arrayDifference(missing_users, found_users); this.debug('log', "Bad users:", bad_users); let bad_data = bad_users.map((userid) => ({[userid]: BlankUser(userid)})); SaveMappedListData(bad_data, BAD_USER_EXPIRATION); mapped_list_data = JSPLib.utility.concat(mapped_list_data, bad_data); } } if (found_users.length) { this.debug('log', "Found users:", found_users); let found_data = found_users.map((userid) => { //Just in case... let default_val = {value: BlankUser(userid)}; return {[userid]: JSPLib.storage.getIndexedSessionData('user-' + userid, default_val).value}; }); mapped_list_data = JSPLib.utility.concat(mapped_list_data, found_data); } return Object.assign({}, ...mapped_list_data); } //Main execution functions ////#A-SHOW async function DisplayPostViews() { var post_views; let post_id = $('.image-container').data('id'); let views_key = `pv-${post_id}`; this.debug('log', "Checking:", post_id); let view_data = await JSPLib.storage.checkLocalDB(views_key, ValidateEntry, MAX_VIEWS_EXPIRATION); if (!view_data) { let post_timestamp = new Date($("#post-information time").attr("datetime")).getTime(); let expiration_time = PostViewsExpiration(post_timestamp); try { post_views = await $.get(`https://isshiki.donmai.us/post_views/${post_id}`); } catch(e) { let error_text = `${e.status} ${e.responseText || e.statusText}`; this.debug('log', "Error:", e.status, e.responseText || e.statusText); $("#dpi-post-views").html(`Views: ${error_text}`).show(); return; } //If the post was created within the hour, then only cache in session storage, else cache normally if (expiration_time === MIN_VIEWS_EXPIRATION) { JSPLib.storage.setStorageData(views_key, {value: post_views, expires: JSPLib.utility.getExpires(expiration_time)}, sessionStorage); } else { JSPLib.storage.saveData(views_key, {value: post_views, expires: JSPLib.utility.getExpires(expiration_time)}); } } else { post_views = view_data.value; } $("#dpi-post-views").html(`Views: ${post_views}`).show(); } async function DisplayTopTagger() { var name_html, top_tagger_id; let $image = $(".image-container"); let uploader_id = $image.data('uploader-id'); let post_id = $image.data('id'); let tag_string = $image.data('tags'); let tag_hash = CryptoJS.MD5(tag_string).toString(); let key_hash = `tt-${post_id}-${tag_hash}`; let data = await JSPLib.storage.checkLocalDB(key_hash, ValidateEntry, TOP_TAGGER_EXPIRATION); if (!data) { this.debug('log', "Cache miss:", key_hash); //Hashed so that it's mutable let current_tags = {tags: tag_string.split(' ')}; let user_tags = DPI.user_tags = {}; let version_order = DPI.version_order = []; let post_versions = await JSPLib.danbooru.submitRequest('post_versions', {search: {post_id}, limit: 1000, only: POSTVER_FIELDS}); if (post_versions && post_versions.length) { post_versions.sort((a, b) => (a.version - b.version)); if (post_versions[0].unchanged_tags.length !== 0) { let true_adds = JSPLib.utility.arrayIntersection(current_tags.tags, post_versions[0].unchanged_tags.split(' ')); PopulateUserTags(current_tags, true_adds, user_tags, version_order, uploader_id); } post_versions.forEach((postver) => { let true_adds = JSPLib.utility.arrayIntersection(postver.added_tags, current_tags.tags); if (true_adds.length) { let updater_id = postver.updater_id || 13; PopulateUserTags(current_tags, true_adds, user_tags, version_order, updater_id); } }); version_order = JSPLib.utility.arrayUnique(version_order); let user_order = Object.keys(user_tags).map(Number).sort((a, b) => (user_tags[b].length - user_tags[a].length)); let top_taggers = user_order.filter((user) => (user_tags[user].length === user_tags[user_order[0]].length)); if (top_taggers.length > 1) { top_taggers.sort((a, b) => (version_order.indexOf(b) - version_order.indexOf(a))); } top_tagger_id = top_taggers[0]; this.debug('log', "Top tagger found:", top_tagger_id); JSPLib.storage.saveData(key_hash, {value: top_tagger_id, expires: JSPLib.utility.getExpires(TOP_TAGGER_EXPIRATION)}); } else { this.debug('log', "Error: No post versions found", post_versions); name_html = "No data!"; } } else { top_tagger_id = data.value; this.debug('log', "Cache hit:", key_hash); } if (top_tagger_id) { if (!(top_tagger_id in DPI.user_promises)) { DPI.user_promises[top_tagger_id] = GetUserData(top_tagger_id); } let user_data = await DPI.user_promises[top_tagger_id]; name_html = RenderUsername(top_tagger_id, user_data); } $("#dpi-top-tagger").html(`Top tagger: ${name_html}`).show(); } ////#A-INDEX async function RenderTooltip(render_promise, instance) { await Promise.all([render_promise, DPI.favorites_promise]); let $target = $(instance.reference); let post_id = $target.closest('.post-preview').data('id'); if (!DPI.favorite_ids.includes(post_id)) { return; } $(instance.popper).find('.fa-heart').removeClass('far').addClass('fas'); } function UpdateThumbnailTitles() { DPI.all_uploaders.then((data) => { $(".post-preview").each((i, entry) => { let uploader_id = $(entry).data('uploader-id'); let $image = $("img", entry); let title = $image.attr('title'); title += ` user:${data[uploader_id].name}`; $image.attr('title', title); }); }); } async function ProcessPostFavorites() { let $post_previews = $(".post-preview"); let post_ids = $post_previews.map((i, entry) => $(entry).data('id')).toArray(); let favorites = await JSPLib.danbooru.submitRequest('favorites', {search: {user_id: DPI.user_id, post_id: post_ids.join(',')}, limit: post_ids.length, only: 'post_id'}); DPI.favorite_ids = JSPLib.utility.getObjectAttributes(favorites, 'post_id'); $post_previews.each((i, entry) => { let $entry = $(entry); let post_id = $entry.data('id'); let is_favorited = DPI.favorite_ids.includes(post_id); $entry.attr('data-is-favorited', is_favorited).data('is_favorited', is_favorited); }); } function ProcessTagStatistics() { let $search_tags = $("#tag-box .search-tag"); let $post_previews = $(".post-preview"); let total_posts = $post_previews.length; let post_tags = $post_previews.map((i, entry) => [$(entry).data('tags').split(' ')]).toArray(); let column_tags = $search_tags.map((i, entry) => $(entry).text().replace(/ /g, '_')).toArray(); let column_info = {}; column_tags.forEach((tag) => { column_info[tag] = post_tags.filter((entry) => entry.includes(tag)).length; }); $search_tags.each((i, entry) => { let tag = column_tags[i]; let tag_percentage = Math.ceil(100 * (column_info[tag] / total_posts)) || 0; let tag_percentage_string = JSPLib.utility.padNumber(tag_percentage, 2) + '%'; let spacing_tyle = (tag_percentage === 100 ? `style="letter-spacing:-2px"` : ""); $(entry).before(` ${tag_percentage_string} `); }); } function ProcessPostStatistics() { let $post_previews = $(".post-preview"); let total_posts = $post_previews.length; let score_list = JSPLib.utility.getDOMAttributes($post_previews, 'score', Number); let faves_list = JSPLib.utility.getDOMAttributes($post_previews, 'fav-count', Number); let general_count = $post_previews.filter("[data-rating=g]").length; let sensitive_count = $post_previews.filter("[data-rating=s]").length; let questionable_count = $post_previews.filter("[data-rating=q]").length; let explicit_count = $post_previews.filter("[data-rating=e]").length; let statistics_html = JSPLib.utility.regexReplace(POST_STATISTICS_TABLE, { SCORE_AVERAGE: JSPLib.utility.setPrecision(JSPLib.statistics.average(score_list), 1) || 0, SCORE_DEVIATION: JSPLib.utility.setPrecision(JSPLib.statistics.standardDeviation(score_list), 1) || 0, FAVES_AVERAGE: JSPLib.utility.setPrecision(JSPLib.statistics.average(faves_list), 1) || 0, FAVES_DEVIATION: JSPLib.utility.setPrecision(JSPLib.statistics.standardDeviation(faves_list), 1) || 0, GENERAL_PERCENTAGE: Math.ceil(100 * (general_count / total_posts)) || 0, SENSITIVE_PERCENTAGE: Math.ceil(100 * (sensitive_count / total_posts)) || 0, QUESTIONABLE_PERCENTAGE: Math.ceil(100 * (questionable_count / total_posts)) || 0, EXPLICIT_PERCENTAGE: Math.ceil(100 * (explicit_count / total_posts)) || 0, PENDING_COUNT: $post_previews.filter('[data-flags*="pending"]').length, BANNED_COUNT: $post_previews.filter('[data-flags*="banned"]').length, FLAGGED_COUNT: $post_previews.filter('[data-flags*="flagged"]').length, DELETED_COUNT: $post_previews.filter('[data-flags*="deleted"]').length, }); $('#dpi-post-statistics').append(statistics_html); } async function ProcessDomainStatistics() { let $domain_table = $(DOMAIN_STATISTICS_TABLE); let post_ids = JSPLib.utility.getDOMAttributes($(".post-preview"), 'id', Number); let posts = await JSPLib.danbooru.submitRequest('posts', {tags: `id:${post_ids.join(',')} status:any`, limit: post_ids.length, only: 'source', expires_in: '3600s'}); let domain_frequency = posts .map((post) => { try { //Will generate an exception for non-URL sources return JSPLib.utility.getDomainName(post.source, 2); } catch (e) { return ""; } }) .filter((host) => (host !== "")) .reduce((total, host) => { total[host] = total[host] || 0; total[host]++; return total; }, {}); let domain_keys = Object.keys(domain_frequency).sort((a, b) => (domain_frequency[b] - domain_frequency[a])); domain_keys.forEach((domain) => { let [class_addon, title_addon] = (domain.length > JSPLib.utility.max_column_characters ? ['class="dpi-domain-overflow"', `title="${domain}"`] : ["", ""]); $('tbody', $domain_table).append( JSPLib.utility.regexReplace(DOMAIN_STATISTICS_ROW, { DOMAIN_NAME: `${JSPLib.utility.maxLengthString(domain)}`, DOMAIN_COUNT: domain_frequency[domain], }) ); }); $('#dpi-domain-statistics').append($domain_table); } ////OTHER function CleanupTasks() { JSPLib.storage.pruneEntries(PROGRAM_SHORTCUT, PROGRAM_DATA_REGEX, PRUNE_EXPIRES); } //Settings functions function RemoteSettingsCallback() { //FIX FOR MENU LIBRARY setTimeout(() => {InitializeChangedSettings();}, 1); } function InitializeChangedSettings() { if (DPI.controller === 'posts' && DPI.action === 'show') { if (JSPLib.menu.hasSettingChanged('post_views_enabled')) { let $post_views = $("#dpi-post-views"); if (DPI.user_settings.post_views_enabled) { if ($post_views.text() === "") { DisplayPostViews(); } else { $post_views.show(); } } else { $post_views.hide(); } } if (JSPLib.menu.hasSettingChanged('top_tagger_enabled')) { let $top_tagger = $("#dpi-top-tagger"); if (DPI.user_settings.top_tagger_enabled) { if ($top_tagger.text() === "") { DisplayTopTagger(); } else { $top_tagger.show(); } } else { $top_tagger.hide(); } } } else if (DPI.controller === 'posts' && DPI.action === 'index') { if (JSPLib.menu.hasSettingChanged('post_statistics_enabled')) { let $post_statistics = $("#dpi-post-statistics"); if (DPI.user_settings.post_statistics_enabled) { if ($post_statistics.children().length === 0) { ProcessPostStatistics(); } else { $post_statistics.show(); } } else { $post_statistics.hide(); } } if (JSPLib.menu.hasSettingChanged('domain_statistics_enabled')) { let $domain_statistics = $('#dpi-domain-statistics'); if (DPI.user_settings.domain_statistics_enabled) { if ($domain_statistics.children().length === 0) { ProcessDomainStatistics(); } else { $domain_statistics.show(); } } else { $domain_statistics.hide(); } } if (JSPLib.menu.hasSettingChanged('tag_statistics_enabled')) { let $tag_statistics = $(".dpi-tag-statistic"); if (DPI.user_settings.tag_statistics_enabled) { if ($tag_statistics.length === 0) { ProcessTagStatistics(); } else { $tag_statistics.show(); } } else { $tag_statistics.hide(); } } //Not handling tooltips or domain source at this time } } function InitializeProgramValues() { Object.assign(DPI, { user_id: Danbooru.CurrentUser.data('id'), basic_tooltips: Danbooru.CurrentUser.data('disable-post-tooltips'), }); return true; } function RenderSettingsMenu() { $('#display-post-info').append(JSPLib.menu.renderMenuFramework(MENU_CONFIG)); $("#dpi-general-settings").append(JSPLib.menu.renderDomainSelectors()); $("#dpi-information-settings").append(JSPLib.menu.renderCheckbox('post_views_enabled')); $("#dpi-information-settings").append(JSPLib.menu.renderCheckbox('top_tagger_enabled')); $("#dpi-information-settings").append(JSPLib.menu.renderCheckbox('post_favorites_enabled')); $("#dpi-tooltip-settings").append(JSPLib.menu.renderCheckbox('basic_post_tooltip')); $("#dpi-tooltip-settings").append(JSPLib.menu.renderCheckbox('advanced_post_tooltip')); $('#dpi-tooltip-settings').append(JSPLib.menu.renderTextinput('post_show_delay', 10)); $('#dpi-tooltip-settings').append(JSPLib.menu.renderTextinput('post_hide_delay', 10)); $("#dpi-statistics-settings").append(JSPLib.menu.renderCheckbox('post_statistics_enabled')); $("#dpi-statistics-settings").append(JSPLib.menu.renderCheckbox('domain_statistics_enabled')); $("#dpi-statistics-settings").append(JSPLib.menu.renderCheckbox('tag_statistics_enabled')); $('#dpi-controls').append(JSPLib.menu.renderCacheControls()); $('#dpi-cache-controls-message').append(JSPLib.menu.renderExpandable("Cache Data details", CACHE_DATA_DETAILS)); $("#dpi-cache-controls").append(JSPLib.menu.renderLinkclick('cache_info', true)); $('#dpi-cache-controls').append(JSPLib.menu.renderCacheInfoTable()); $("#dpi-cache-controls").append(JSPLib.menu.renderLinkclick('purge_cache', true)); $('#dpi-controls').append(JSPLib.menu.renderCacheEditor(true)); $('#dpi-cache-editor-message').append(JSPLib.menu.renderExpandable("Program Data details", PROGRAM_DATA_DETAILS)); $("#dpi-cache-editor-controls").append(JSPLib.menu.renderKeyselect('data_source', true)); $("#dpi-cache-editor-controls").append(JSPLib.menu.renderDataSourceSections()); $("#dpi-section-indexed-db").append(JSPLib.menu.renderKeyselect('data_type', true)); $("#dpi-section-local-storage").append(JSPLib.menu.renderCheckbox('raw_data', true)); $("#dpi-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.expandableClick(); JSPLib.menu.purgeCacheClick(); 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(DPI, RenderSettingsMenu, preload)) return; if (DPI.controller === 'posts' && DPI.action === 'show') { $('#post-information #post-info-score').after(POST_VIEWS_LINE); $('#post-information #post-info-uploader').after(USER_NAMES_LINE); if (DPI.user_settings.post_views_enabled) { DisplayPostViews(); } if (DPI.user_settings.top_tagger_enabled) { DisplayTopTagger(); } } else if (DPI.controller === 'posts' && DPI.action === 'index') { $('#tag-box').after(POST_INDEX_STATISTICS); if (DPI.user_settings.post_statistics_enabled) { ProcessPostStatistics(); } if (DPI.user_settings.domain_statistics_enabled) { ProcessDomainStatistics(); } if (DPI.user_settings.tag_statistics_enabled) { ProcessTagStatistics(); } JSPLib.utility.setCSSStyle(POST_INDEX_CSS, 'program'); } if (DPI.user_settings.post_favorites_enabled) { DPI.favorites_promise = ProcessPostFavorites(); } if (!DPI.basic_tooltips && DPI.user_settings.advanced_post_tooltip) { if (DPI.user_settings.post_favorites_enabled) { Danbooru.PostTooltip.on_show = JSPLib.utility.hijackFunction(Danbooru.PostTooltip.on_show, RenderTooltip); } Danbooru.PostTooltip.SHOW_DELAY = DPI.user_settings.post_show_delay; Danbooru.PostTooltip.HIDE_DELAY = DPI.user_settings.post_hide_delay; if (document.body._tippy) { $(document).off("click.danbooru.postTooltip"); document.body._tippy.destroy(); Danbooru.PostTooltip.initialize(); } } else if (DPI.basic_tooltips && DPI.user_settings.basic_post_tooltip) { let all_uploaders = JSPLib.utility.arrayUnique(JSPLib.utility.getDOMAttributes($(".post-preview"), 'uploader-id')); DPI.all_uploaders = GetUserListData(all_uploaders); UpdateThumbnailTitles(); } JSPLib.statistics.addPageStatistics(PROGRAM_NAME); JSPLib.load.noncriticalTasks(CleanupTasks); } /****Function decoration****/ [ Main, GetUserData, DisplayPostViews, DisplayTopTagger, GetUserListData, ValidateEntry, ] = JSPLib.debug.addFunctionLogs([ Main, GetUserData, DisplayPostViews, DisplayTopTagger, GetUserListData, ValidateEntry, ]); [ RenderSettingsMenu, DisplayPostViews, DisplayTopTagger, GetUserListData, ] = JSPLib.debug.addFunctionTimers([ //Sync RenderSettingsMenu, //Async DisplayPostViews, DisplayTopTagger, GetUserListData, ]); /****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 = DPI; JSPLib.menu.program_data_regex = PROGRAM_DATA_REGEX; JSPLib.menu.program_data_key = PROGRAM_DATA_KEY; JSPLib.menu.settings_callback = RemoteSettingsCallback; JSPLib.menu.settings_config = SETTINGS_CONFIG; JSPLib.menu.control_config = CONTROL_CONFIG; //Export JSPLib JSPLib.load.exportData(PROGRAM_NAME, DPI); /****Execution start****/ JSPLib.load.programInitialize(Main, {program_name: PROGRAM_NAME, required_variables: PROGRAM_LOAD_REQUIRED_VARIABLES, optional_selectors: PROGRAM_LOAD_OPTIONAL_SELECTORS});