`;
const STATUS_MARKER = '';
const MAIN_COUNTER = '( 0 , 0 )';
const TWEET_INDICATORS = 'ⒶⓉⓐⓣ';
const LOAD_COUNTER = 'Loading ( ... )';
const PROFILE_USER_ID = 'User ID - %s';
const PROFILE_USER_VIEW = 'Viewed user - %s';
const PROFILE_STREAM_VIEW = 'Viewed stream - %s';
//Message constants
const SAVE_HELP = "L-Click to save current settings. (Shortcut: Alt+S)";
const RESET_HELP = "L-Click to reset settings to default. (Shortcut: Alt+R)";
const SETTINGS_HELP = "L-Click to open settings menu. (Shortcut: Alt+M)";
const CLOSE_HELP = "L-Click to close. (Shortcut: Alt+C)";
const NO_MATCH_HELP = "no sources: L-click, manual add posts";
const NO_RESULTS_HELP = "no results: L-click, reset IQDB/Sauce results";
const CHECK_URL_HELP = "URL: L-click, query Danbooru for URL match";
const CHECK_IQDB_HELP = "IQDB: L-click, query Danbooru for image match";
const CHECK_SAUCE_HELP = "Sauce: L-click, query SauceNAO for image match";
const CONFIRM_DELETE_HELP = "postlink: L-click, add/delete info; R-click, open postlink";
const CONFIRM_IQDB_HELP = "postlink: L-click, confirm results; R-click open postlink";
const MERGE_RESULTS_HELP = "Merge: L-click, perform another query and merge with current results";
const IQDB_SELECT_HELP = "Select posts that aren't valid IQDB matches.\nClick the colored postlink when finished to confirm.";
const POST_SELECT_HELP = "Select posts for deletion by clicking the thumbnail.\nLeaving the Delete all checkbox on will select all posts.\nUnsetting that checkbox allows adding posts to the current set.\nClick the colored postlink when finished to delete/add posts.";
const INSTALL_DATABASE_HELP = "L-Click to install database.";
const UPGRADE_DATABASE_HELP = "L-Click to upgrade database.";
const MANUAL_INSTALL_HELP = `Visit topic #${DANBOORU_TOPIC_ID} for manual install file.\n(see settings menu for link).`;
const DATABASE_VERSION_HELP = "L-Click to set record position to latest on Danbooru.\nR-Click to open page to Danbooru records.";
const UPDATE_RECORDS_HELP = "L-Click to update records to current.";
const MUST_INSTALL_HELP = "The database must be installed before the script is fully functional.";
const REFRESH_RECORDS_HELP = "L-Click to refresh record count.";
const AVAILABLE_SAUCE_HELP = "Shows the number of API requests remaining.\nOnly shown after use of the Sauce link.\nResults are kept for only 1 hour.";
const HIGHLIGHTS_HELP = "L-Click to toggle Tweet hiding/fading. (Shortcut: Alt+H)";
const VIEWS_HELP = "L-Click to toggle borders on viewed Tweets. (Shortcut: Alt+V)";
const FADE_HIGHLIGHT_HELP = "L-Click '-' to decrease fade level. (Shortcut: Alt+-)\nL-Click '+' to increase fade level. (Shortcut: Alt+=)";
const HIDE_HIGHLIGHT_HELP = "L-Click '-' to decrease hide level. (Shortcut: Alt+[)\nL-Click '+' to increase hide level. (Shortcut: Alt+])";
const AUTO_IQDB_HELP = "L-Click to toggle auto-IQDB click. (Shortcut: Alt+Q)";
const INDICATOR_HELP = "L-Click to toggle display of Tweet mark/count controls. (Shortcut: Alt+I)";
const LOCKPAGE_HELP = "L-Click to prevent navigating away from the page (does not prevent Twitter navigation).";
const ERROR_MESSAGES_HELP = "L-Click to see full error messages.";
const STATISTICS_HELP = 'L-Click any category heading to narrow down results.\nL-Click "Total" category to reset results.';
const INSTALL_MENU_TEXT = "Must install DB!";
const SERVER_ERROR = "Failed to connect to remote server to get latest %s!";
const INSTALL_CONFIRM = JSPLib.utility.trim`
This will install the database (%s, %s).
This can take a couple of minutes.
Click OK when ready.`;
const UPGRADE_CONFIRM = JSPLib.utility.trim`
This will upgrade the database to (%s, %s).
Old database is at (%s, %s).
This can take a couple of minutes.
Click OK when ready.`;
const CURRENT_RECORDS_CONFIRM = JSPLib.utility.trim`
This will keep querying Danbooru until the records are current.
Depending on the current position, this could take several minutes.
Moving focus away from the page will halt the process.
Continue?`;
const CURRENT_POSTVER_CONFIRM = JSPLib.utility.trim`
This will query Danbooru for the latest record position to use.
This may potentially cause change records to be missed.
Continue?`;
const MANUAL_ADD_PROMPT = "Enter the post IDs of matches. (separated by commas)";
const CONFIRM_SAVE_PROMPT = "Save the following post IDs? (separate by comma, empty to reset link)";
const CONFIRM_DELETE_PROMPT = JSPLib.utility.trim`
The following posts will be deleted: %s
Save the following post IDs? (separate by comma, empty to delete)`;
//Database constants
const SERVER_DATABASE_URL = 'https://drive.google.com/uc?export=download&id=16YapNscZ0W-tZaRelYF2kDtR31idUd_p';
const SERVER_PURGELIST_URL = 'https://drive.google.com/uc?export=download&id=1uFixlRryOGUzhfvU6nbrkNRAQC4VvO10';
const DATABASE_INFO_URL = 'https://drive.google.com/uc?export=download&id=1evAJM-K6QpHg52997PbXf-bptImLgHDs';
//Time constants
const STORAGE_DELAY = 1; //Don't save lists synchronously since large lists delay UI response
const JQUERY_DELAY = 1; //For jQuery updates that should not be done synchronously
const TWITTER_DELAY = 100; //Give twitter handler some time to change the page
const TIMER_POLL_INTERVAL = 100;
const QUEUE_POLL_INTERVAL = 500;
const PROGRAM_RECHECK_INTERVAL = JSPLib.utility.one_second;
const VIEWCOUNT_RECENT_DURATION = JSPLib.utility.one_minute * 5;
const POST_VERSIONS_CALLBACK = JSPLib.utility.one_second * 5;
const PAGE_REFRESH_TIMEOUT = JSPLib.utility.one_second * 5;
const SAUCE_EXPIRES = JSPLib.utility.one_hour;
const MIN_POST_EXPIRES = JSPLib.utility.one_day;
const MAX_POST_EXPIRES = JSPLib.utility.one_month;
const USER_EXPIRES = JSPLib.utility.one_month;
const VIEW_EXPIRES = JSPLib.utility.one_month;
const SIMILAR_EXPIRES = JSPLib.utility.one_day;
const VIDEO_EXPIRES = JSPLib.utility.one_week;
const LENGTH_RECHECK_EXPIRES = JSPLib.utility.one_hour;
const USER_PROFILE_RECHECK_EXPIRES = JSPLib.utility.one_month;
const DATABASE_RECHECK_EXPIRES = JSPLib.utility.one_day;
const BADVER_RECHECK_EXPIRES = JSPLib.utility.one_day;
const PRUNE_RECHECK_EXPIRES = JSPLib.utility.one_hour * 6;
const PROFILE_VIEWS_CALLBACK = JSPLib.utility.one_second * 10;
//Regex constants
var TWITTER_ACCOUNT = String.raw`[\w-]+`;
var TWITTER_ID = String.raw`\d+`;
var QUERY_END = String.raw`(?:\?|$)`;
const TWEET_REGEX = XRegExp.tag('g')`^https://twitter\.com/[\w-]+/status/(\d+)$`;
const TWEET_URL_REGEX = XRegExp.tag('g')`^https://twitter\.com/[\w-]+/status/\d+`;
const SOURCE_TWITTER_REGEX = XRegExp.tag('g')`^source:https://twitter\.com/[\w-]+/status/(\d+)$`;
const IMAGE_REGEX = XRegExp.tag()`(https://pbs\.twimg\.com/media/[\w-]+\?format=(?:jpg|png|gif)&name=)(.+)`;
const BANNER_REGEX = XRegExp.tag()`https://pbs\.twimg\.com/profile_banners/(\d+)/\d+/`;
const HANDLED_IMAGES = [{
regex: XRegExp.tag()`^https://pbs\.twimg\.com/(media|tweet_video_thumb)/([^.?]+)`,
format: 'https://pbs.twimg.com/%s/%s.%s',
arguments: (match,extension)=>[match[1], match[2], extension[0]],
},{
regex: XRegExp.tag()`^https://pbs\.twimg\.com/ext_tw_video_thumb/(\d+)/(\w+)/img/([^.?]+)`,
format: 'https://pbs.twimg.com/ext_tw_video_thumb/%s/%s/img/%s.jpg',
arguments: (match)=>[match[1], match[2], match[3]],
},{
regex: XRegExp.tag()`^https://pbs\.twimg\.com/amplify_video_thumb/(\d+)/img/([^.?]+)`,
format: 'https://pbs.twimg.com/amplify_video_thumb/%s/img/%s.jpg',
arguments: (match)=>[match[1], match[2]],
}];
const UNHANDLED_IMAGES = [
XRegExp.tag()`^https://pbs\.twimg\.com/profile_images/`,
XRegExp.tag()`^https://[^.]+\.twimg\.com/emoji/`,
XRegExp.tag()`^https://pbs.twimg.com/ad_img/`,
XRegExp.tag()`^https://abs.twimg.com/hashflags/`,
XRegExp.tag()`^https://pbs.twimg.com/card_img/`,
];
var ALL_PAGE_REGEXES = {
main: {
format: ' {{no_match}} ({{main_account}}) {{end}} ',
subs: {
main_account: TWITTER_ACCOUNT,
no_match: '(?!search|home)',
end: QUERY_END,
}
},
media: {
format: ' ( {{media_account}} ) {{media}} {{end}} ',
subs: {
media_account: TWITTER_ACCOUNT,
media: '/media',
end: QUERY_END,
}
},
search: {
format: ' {{search}} ( {{search_query}} ) ',
subs: {
search: String.raw`search\?`,
search_query: String.raw`.*?\bq=.+`,
}
},
tweet: {
format: ' ( {{tweet_account}} ) {{status}} ( {{tweet_id}} ) {{end}} ',
subs: {
tweet_account: TWITTER_ACCOUNT,
tweet_id: TWITTER_ID,
status: '/status/',
end: QUERY_END,
}
},
web_tweet: {
format: ' {{status}} ( {{web_tweet_id}} ) {{end}} ',
subs: {
web: TWITTER_ACCOUNT,
web_tweet_id: TWITTER_ID,
status: 'i/web/status/',
end: QUERY_END,
}
},
hashtag: {
format: ' {{hashtag}} ( {{hashtag_hash}} ) {{end}} ',
subs: {
hashtag: 'hashtag/',
hashtag_hash: '.+?',
end: QUERY_END,
}
},
list: {
format: ' ( {{list_account}} ) {{list}} ( {{list_id}} ) {{end}} ',
subs: {
list_account: TWITTER_ACCOUNT,
list_id: String.raw`[\w-]+`,
list: '/lists/',
end: QUERY_END,
}
},
home: {
format: ' {{home}} {{end}} ',
subs: {
home: 'home',
end: QUERY_END,
}
},
likes: {
format: ' ( {{likes_account}} ) {{likes}} {{end}} ',
subs: {
likes_account: TWITTER_ACCOUNT,
likes: '/likes',
end: QUERY_END,
}
},
replies: {
format: ' ( {{replies_account}} ) {{replies}} {{end}} ',
subs: {
replies_account: TWITTER_ACCOUNT,
replies: '/with_replies',
end: QUERY_END,
}
},
photo: {
format: ' ( {{photo_account}} ) {{status}} ( {{photo_id}} ) {{type}} ( {{photo_index}} ) {{end}} ',
subs: {
photo_account: TWITTER_ACCOUNT,
photo_id: TWITTER_ID,
photo_index: String.raw`\d`,
type: '/(?:photo|video)/',
status: '/status/',
end: QUERY_END,
}
},
moments: {
format: ' {{moments}} ( {{moment_id}} ) {{end}} ',
subs: {
moment_id: TWITTER_ID,
moments: 'i/moments/',
end: QUERY_END,
}
},
topics: {
format: ' {{topics}} ( {{topic_id}} ) {{end}} ',
subs: {
topic_id: TWITTER_ID,
topics: 'i/topics/',
end: QUERY_END,
}
},
display: {
format: ' {{display}} {{end}} ',
subs: {
display: 'i/display',
end: QUERY_END,
}
},
};
//Network constants
const TWEET_GRAPHQL_PARAMS = {
with_rux_injections: false,
includePromotedContent: true,
withCommunity: false,
withQuickPromoteEligibilityTweetFields: true,
withBirdwatchNotes: false,
withSuperFollowsUserFields: true,
withDownvotePerspective: false,
withReactionsMetadata: false,
withReactionsPerspective: false,
withSuperFollowsTweetFields: true,
withVoice: true,
withV2Timeline: true,
__fs_responsive_web_like_by_author_enabled: false,
__fs_dont_mention_me_view_api_enabled: true,
__fs_interactive_text_enabled: true,
__fs_responsive_web_uc_gql_enabled: false,
__fs_responsive_web_edit_tweet_api_enabled: false
};
const QUERY_LIMIT = 100;
const QUERY_BATCH_NUM = 5;
const QUERY_BATCH_SIZE = QUERY_LIMIT * QUERY_BATCH_NUM;
const POST_FIELDS = 'id,uploader_id,score,fav_count,rating,tag_string,created_at,preview_file_url,source,file_ext,file_size,image_width,image_height,uploader[name]';
const POSTVER_FIELDS = 'id,updated_at,post_id,version,source,source_changed,added_tags,removed_tags';
const PROFILE_FIELDS = 'id,level';
//DOM constants
const HIGHLIGHT_CONTROLS = ['enable', 'disable', 'unavailable'];
const VIEW_CONTROLS = ['enable', 'disable'];
const IQDB_CONTROLS = ['enable', 'disable', 'active', 'unavailable'];
const INDICATOR_CONTROLS = ['enable', 'disable'];
const ALL_INDICATOR_TYPES = ['mark-artist', 'mark-tweet', 'count-artist', 'count-tweet'];
const BASE_DIALOG_WIDTH = 45;
const BASE_QTIP_WIDTH = 10;
//Queue constants
const QUEUED_STORAGE_REQUESTS = [];
const SAVED_STORAGE_REQUESTS = [];
const CACHED_STORAGE_REQUESTS = {};
const CACHE_STORAGE_TYPES = ['get','check'];
const STORAGE_DATABASES = {
danbooru: JSPLib.storage.danboorustorage,
twitter: JSPLib.storage.twitterstorage,
};
const QUEUED_NETWORK_REQUESTS = [];
const SAVED_NETWORK_REQUESTS = [];
const NETWORK_REQUEST_DICT = {
posts: {
data_key: "id",
params (post_ids) {
return {
tags: 'status:any id:' + post_ids.join(','),
only: POST_FIELDS,
limit: 200,
};
},
},
users: {
data_key: "id",
params (user_ids) {
return {
search: {
id: user_ids.join(','),
},
only: 'id,name',
limit: 1000,
};
},
},
};
//Other constants
const STREAMING_PAGES = ['home', 'main', 'likes', 'replies', 'media', 'list', 'search', 'hashtag', 'moments', 'topics'];
const MEDIA_TYPES = ['images', 'media', 'videos'];
const ALL_LISTS = {
highlight: 'no-highlight-list',
iqdb: 'auto-iqdb-list',
artist: 'artist-list',
tweet: 'tweet-list'
};
const GOLD_LEVEL = 30;
//UI constants
const PREVIEW_QTIP_SETTINGS = {
style: {
classes: 'qtiptisas-twitter ntisas-preview-tooltip',
},
position: {
my: 'top center',
at: 'bottom center',
viewport: true,
},
show: {
delay: 500,
solo: true,
},
hide: {
delay: 250,
fixed: true,
leave: false, // Prevent hiding when cursor hovers a browser tooltip
}
};
const IMAGE_QTIP_SETTINGS = {
style: {
classes: 'qtiptisas-twitter ntisas-image-tooltip',
},
position: {
my: 'center',
at: 'center',
viewport: false,
},
show: {
delay: 1000,
solo: true,
},
hide: {
delay: 100,
fixed: true,
}
};
const CONFIRM_DIALOG_SETTINGS = {
title: "Image select",
modal: true,
resizable:false,
autoOpen: false,
classes: {
'ui-dialog': 'ntisas-dialog',
'ui-dialog-titlebar-close': 'ntisas-dialog-close'
},
open: function () {
this.promiseConfirm = new Promise((resolve)=>{this.resolveConfirm = resolve;});
},
close: function () {
this.resolveConfirm && this.resolveConfirm(false);
},
buttons: {
'Submit': function () {
this.resolveConfirm && this.resolveConfirm(true);
$(this).dialog('close');
},
'Cancel': function () {
this.resolveConfirm && this.resolveConfirm(false);
$(this).dialog('close');
}
}
};
const MENU_DIALOG_SETTINGS = {
modal: true,
resizable:false,
autoOpen: false,
width: 1000,
height: 800,
classes: {
'ui-dialog': 'ntisas-dialog',
'ui-dialog-titlebar-close': 'ntisas-dialog-close'
},
open: ()=>{
NTISAS.opened_menu = true;
},
close: ()=>{
NTISAS.opened_menu = false;
},
buttons: {
//Save and reset are bound separately
'Save': (()=>{}),
'Factory reset': (()=>{}),
'Close': function () {
$(this).dialog('close');
}
}
};
const MENU_DIALOG_BUTTONS = {
'Save': {
id: 'ntisas-commit',
title: SAVE_HELP
},
'Factory reset': {
id: 'ntisas-resetall',
title: RESET_HELP
},
'Close': {
id: null,
title: CLOSE_HELP
}
};
//Validation constants
const POST_CONSTRAINTS = {
entry: JSPLib.validate.hashentry_constraints,
value: {
id: JSPLib.validate.id_constraints,
uploaderid: JSPLib.validate.id_constraints,
uploadername: JSPLib.validate.stringonly_constraints,
score: JSPLib.validate.integer_constraints,
favcount: JSPLib.validate.counting_constraints,
rating: JSPLib.validate.inclusion_constraints(['s', 'q', 'e']),
tags: JSPLib.validate.stringonly_constraints,
created: JSPLib.validate.counting_constraints,
thumbnail: JSPLib.validate.stringonly_constraints,
source: JSPLib.validate.stringonly_constraints,
ext: JSPLib.validate.inclusion_constraints(['jpg', 'png', 'gif', 'mp4', 'webm']),
size: JSPLib.validate.counting_constraints,
width: JSPLib.validate.counting_constraints,
height: JSPLib.validate.counting_constraints
}
};
const USER_CONSTRAINTS = {
entry: JSPLib.validate.hashentry_constraints,
value: {
id: JSPLib.validate.id_constraints,
name: JSPLib.validate.stringonly_constraints,
}
};
const VIEW_CONSTRAINTS = {
entry: JSPLib.validate.hashentry_constraints,
value: {
count: JSPLib.validate.counting_constraints,
viewed: JSPLib.validate.counting_constraints,
},
};
const SIMILAR_CONSTRAINTS = {
expires: JSPLib.validate.expires_constraints,
value: JSPLib.validate.boolean_constraints
};
const VIDEO_CONSTRAINTS = {
expires: JSPLib.validate.expires_constraints,
value: JSPLib.validate.stringnull_constraints
};
const TWUSER_CONSTRAINTS = {
expires: JSPLib.validate.expires_constraints,
value: JSPLib.validate.stringonly_constraints,
};
const SAUCE_CONSTRAINTS = {
expires: JSPLib.validate.expires_constraints,
value: JSPLib.validate.integer_constraints
};
const COLOR_CONSTRAINTS = {
base_color: JSPLib.validate.array_constraints({is: 3}),
text_color: JSPLib.validate.array_constraints({is: 3}),
background_color: JSPLib.validate.array_constraints({is: 3})
};
const PROFILE_CONSTRAINTS = {
id: JSPLib.validate.id_constraints,
level: JSPLib.validate.id_constraints,
};
const DATABASE_CONSTRAINTS = {
post_version: JSPLib.validate.id_constraints,
timestamp: JSPLib.validate.timestamp_constraints,
};
/****Functions****/
function ValidateEntry(key,entry) {
if (!JSPLib.validate.validateIsHash(key, entry)) {
return false;
}
if (key.match(/^post-/)) {
return ValidateTypeEntry(key, entry, POST_CONSTRAINTS);
}
if (key.match(/^user-\d+/)) {
return ValidateTypeEntry(key, entry, USER_CONSTRAINTS);
}
if (key.match(/^view-/) || key.match(/^((main|media|likes|replies)-stream|user)-view-/)) {
return ValidateTypeEntry(key, entry, VIEW_CONSTRAINTS);
}
if (key.match(/^(iqdb|sauce)-/)) {
return JSPLib.validate.validateHashEntries(key, entry, SIMILAR_CONSTRAINTS);
}
if (key.match(/^video-/)) {
return JSPLib.validate.validateHashEntries(key, entry, VIDEO_CONSTRAINTS);
}
if (key.match(/^twuser-/)) {
return JSPLib.validate.validateHashEntries(key, entry, TWUSER_CONSTRAINTS);
}
if (key === 'ntisas-available-sauce') {
return JSPLib.validate.validateHashEntries(key, entry, SAUCE_CONSTRAINTS);
}
this.debug('warn', "Bad key!");
return false;
}
function ValidateExpiration(key) {
if (key.match(/^post-/)) {
return MAX_POST_EXPIRES;
}
if (key.match(/^user-\d+/)) {
return USER_EXPIRES;
}
if (key.match(/^view-/)) {
return VIEW_EXPIRES;
}
if (key.match(/^((main|media|likes|replies)-stream|user)-view-/)) {
return JSPLib.utility.one_year;
}
if (key.match(/^(iqdb|sauce)-/)) {
return SIMILAR_EXPIRES;
}
if (key.match(/^video-/)) {
return VIDEO_EXPIRES;
}
return 0;
}
function ValidateTypeEntry(key,entry,constraints) {
if (!JSPLib.validate.validateHashEntries(key, entry, constraints.entry)) {
return false;
}
return JSPLib.validate.validateHashEntries(key + '.value', entry.value, constraints.value);
}
function ValidateProgramData(key,entry) {
var checkerror = [], check;
switch (key) {
case 'ntisas-user-settings':
checkerror = JSPLib.menu.validateUserSettings(entry);
break;
case 'ntisas-database-length':
if (!Number.isInteger(entry)) {
checkerror = ["Value is not an integer."];
}
break;
case 'ntisas-timeout':
case 'ntisas-database-recheck':
case 'ntisas-badver-recheck':
case 'ntisas-length-recheck':
case 'ntisas-user-profile-recheck':
case 'ntisas-recent-timestamp':
case 'ntisas-process-semaphore-checkuser':
case 'ntisas-process-semaphore-purgebad':
case 'ntisas-process-semaphore-badvers':
case 'ntisas-process-semaphore-records':
case 'ntisas-process-semaphore-checkpost':
case 'ntisas-process-semaphore-postvers':
case 'ntisas-prune-expires':
if (!Number.isInteger(entry)) {
checkerror = ["Value is not an integer."];
} else if (entry < 0) {
checkerror = ["Value is not greater than or equal to zero."];
}
break;
case 'ntisas-postver-lastid':
case 'ntisas-badver-lastid':
if (!JSPLib.validate.validateID(entry)) {
checkerror = ["Value is not a valid ID."];
}
break;
case 'ntisas-overflow':
case 'ntisas-purge-bad':
case 'ntisas-indicator-controls':
if (!JSPLib.validate.isBoolean(entry)) {
checkerror = ["Value is not a boolean."];
}
break;
case 'ntisas-artist-list':
case 'ntisas-tweet-list':
case 'ntisas-auto-iqdb-list':
case 'ntisas-no-highlight-list':
return JSPLib.validate.validateArrayValues(key, entry, JSPLib.validate.basic_stringonly_validator);
case 'ntisas-user-data':
check = validate(entry, PROFILE_CONSTRAINTS);
if (check) {
checkerror = [check];
break;
}
break;
case 'ntisas-color-style':
check = validate(entry, COLOR_CONSTRAINTS);
if (check) {
checkerror = [check];
break;
}
for (let key in entry) {
if (!VaildateColorArray(entry[key])) {
checkerror.push(`${key} does not contain a valid color array.`);
}
}
break;
case 'ntisas-side-selection':
if (!['info', 'controls', 'statistics'].includes(entry)) {
checkerror = ["Not a valid selection."];
}
break;
default:
checkerror = ["Not a valid program data key."];
}
if (checkerror.length) {
JSPLib.validate.outputValidateError(key, checkerror);
return false;
}
return true;
}
function CorrectStringArray(name,stringlist) {
if (!Array.isArray(stringlist)) {
this.debug('log', "Value is not an array.");
return [];
}
let correctlist = stringlist.filter(JSPLib.validate.isString);
if (stringlist.length !== correctlist.length) {
SetLocalData('ntisas-' + name, correctlist);
JSPLib.debug.debugExecute(()=>{
let bad_values = JSPLib.utility.arrayDifference(stringlist, correctlist);
this.debug('log', "Bad values found:", name, bad_values);
});
}
return correctlist;
}
function VaildateColorArray(array) {
return array.every((val)=>{
let parse = Number(val);
return JSPLib.validate.isString(val) &&
Number.isInteger(parse) &&
parse >=0 && parse <= 255;
});
}
//Library functions
////NONE
//Helper functions
////Make setting all of these into a library function
function GetLocalData(key,default_val) {
return JSPLib.storage.getStorageData(key, localStorage, default_val);
}
function CheckLocalData(key,default_val) {
return JSPLib.storage.checkStorageData(key, ValidateProgramData, localStorage, default_val);
}
function SetLocalData(key,data) {
JSPLib.storage.setStorageData(key, data, localStorage);
}
function InvalidateLocalData(key) {
JSPLib.storage.invalidateStorageData(key, localStorage);
}
function GetSessionTwitterData(tweet_id) {
return JSPLib.storage.getIndexedSessionData('tweet-' + tweet_id, [], STORAGE_DATABASES.twitter);
}
function GetAPIData(key,id,value) {
if (API_DATA === undefined || !(key in API_DATA) || !(id in API_DATA[key])) {
return null;
}
return (value ? API_DATA[key][id][value] : API_DATA[key][id]);
}
function GetNumericTimestamp(timestamp) {
return GetDateString(timestamp) + GetTimeString(timestamp);
}
function GetDateString(timestamp) {
let time_obj = new Date(timestamp);
return `${time_obj.getFullYear()}${JSPLib.utility.padNumber(time_obj.getMonth() + 1, 2)}${JSPLib.utility.padNumber(time_obj.getDate() ,2)}`;
}
function GetTimeString(timestamp) {
let time_obj = new Date(timestamp);
return `${JSPLib.utility.padNumber(time_obj.getHours(), 2)}${JSPLib.utility.padNumber(time_obj.getMinutes(), 2)}`;
}
function ParseQueries(str) {
return str.split(' ').reduce(function (params, param) {
var paramSplit = param.split(':');
params[paramSplit[0]] = paramSplit[1];
return params;
}, {});
}
function TimeAgo(timestamp) {
let time_interval = Date.now() - timestamp;
if (time_interval < JSPLib.utility.one_hour) {
return JSPLib.utility.setPrecision(time_interval / JSPLib.utility.one_minute, 2) + " minutes ago";
} else if (time_interval < JSPLib.utility.one_day) {
return JSPLib.utility.setPrecision(time_interval / JSPLib.utility.one_hour, 2) + " hours ago";
} else if (time_interval < JSPLib.utility.one_month) {
return JSPLib.utility.setPrecision(time_interval / JSPLib.utility.one_day, 2) + " days ago";
} else if (time_interval < JSPLib.utility.one_year) {
return JSPLib.utility.setPrecision(time_interval / JSPLib.utility.one_month, 2) + " months ago";
} else {
return JSPLib.utility.setPrecision(time_interval / JSPLib.utility.one_year, 2) + " years ago";
}
}
function GetPostVersionsExpiration() {
return NTISAS.user_settings.recheck_interval * JSPLib.utility.one_minute;
}
function WasOverflow() {
return CheckLocalData('ntisas-overflow', false);
}
//This needs its own separate validation because it should not be exported
function GetRemoteDatabase() {
let data = GetLocalData('ntisas-remote-database');
return (JSPLib.validate.validateHashEntries('ntisas-remote-database', data, DATABASE_CONSTRAINTS) ? data : null);
}
function IsTISASInstalled() {
return GetRemoteDatabase() !== null;
}
function GetUserIdent() {
if (API_DATA.has_data) {
return [NTISAS.user_id, [NTISAS.user_id, NTISAS.account]];
} else {
return [NTISAS.account, [NTISAS.account]];
}
}
function PageRegex() {
if (!NTISAS.page_regex) {
let built_regexes = Object.assign({}, ...Object.keys(ALL_PAGE_REGEXES).map((page)=>{
//Match at beginning of string with Twitter URL
let regex = XRegExp.build(`^ https://twitter.com/ ` + ALL_PAGE_REGEXES[page].format, ALL_PAGE_REGEXES[page].subs, 'x');
//Add page named capturing group
return {[page]: XRegExp.build(' ( {{' + page + '}} ) ', {[page]: regex}, 'x')};
}));
//Combine all regexes
let all_format = Object.keys(built_regexes).map((page) => (' {{' + page + '}} ')).join('|');
let all_regex = XRegExp.build(all_format, built_regexes, 'x');
//Add overall capturing group...
NTISAS.page_regex = XRegExp.build(' ( {{site}} )', {site: all_regex}, 'x');
}
return NTISAS.page_regex;
}
function DisplayHighlights() {
return NTISAS.user_settings.score_highlights_enabled && IsMediaTimeline();
}
function IsQuerySettingEnabled(setting,type) {
let type_key = (type === 'iqdb' ? 'IQDB' : type) + '_settings';
return NTISAS.user_settings[type_key].includes(setting);
}
function MapPostData(posts) {
return posts.map(MapPost);
}
function MapPost(post) {
return {
id: post.id,
uploaderid: post.uploader_id,
uploadername: ('uploader' in post ? post.uploader.name : null),
score: post.score,
favcount: post.fav_count,
rating: post.rating,
tags: post.tag_string,
created: new Date(post.created_at).getTime(),
thumbnail: post.preview_file_url,
source: post.source,
ext: post.file_ext,
size: post.file_size,
width: post.image_width,
height: post.image_height
};
}
function MapSimilar(post,score) {
return {
score: score,
post: post
};
}
function GetLinkTitle(post) {
let tags = JSPLib.utility.HTMLEscape(post.tags);
let age = JSPLib.utility.HTMLEscape(`age:"${TimeAgo(post.created)}"`);
return `user:${post.uploadername} score:${post.score} favcount:${post.favcount} rating:${post.rating} ${age} ${tags}`;
}
function GetMultiLinkTitle(posts) {
let title = [];
posts.forEach((post)=>{
let age = JSPLib.utility.HTMLEscape(`age:"${TimeAgo(post.created)}"`);
title.push(`post #${post.id} - user:${post.uploadername} score:${post.score} favcount:${post.favcount} rating:${post.rating} ${age}`);
});
return title.join('\n');
}
function GetCustomQuery() {
return (NTISAS.user_settings.custom_order_enabled && (NTISAS.user_data.level >= GOLD_LEVEL) ? '+order%3Acustom' : '');
}
function GetPostVersionsLastID(type) {
//Get the program last ID if it exists
let postver_lastid = CheckLocalData(`ntisas-${type}-lastid`, NTISAS.database_info && NTISAS.database_info.post_version);
if (NTISAS.user_settings.bypass_server_mode && !NTISAS.database_info) {
return postver_lastid;
}
//Select the largest of the program lastid and the database lastid
let max_postver_lastid = Math.max(postver_lastid, NTISAS.database_info.post_version);
if (postver_lastid !== max_postver_lastid) {
SetLocalData(`ntisas-${type}-lastid`, max_postver_lastid);
}
return max_postver_lastid;
}
async function GetTotalRecords(manual=false) {
if (manual || JSPLib.concurrency.checkTimeout('ntisas-length-recheck', LENGTH_RECHECK_EXPIRES)) {
let database_length = await JSPLib.storage.twitterstorage.length();
SetLocalData('ntisas-database-length', database_length);
JSPLib.concurrency.setRecheckTimeout('ntisas-length-recheck', LENGTH_RECHECK_EXPIRES);
}
return GetLocalData('ntisas-database-length', 0);
}
function GetImageAttributes(image_url) {
let base_url = image_url.split(':orig')[0];
NTISAS.image_data = NTISAS.image_data || {};
return new Promise((resolve)=>{
if (image_url in NTISAS.image_data) {
resolve(NTISAS.image_data[image_url]);
}
let size_promise = JSPLib.network.getDataSize(image_url);
let dimensions_promise;
if (base_url in NTISAS.tweet_images) {
this.debug('log', "Found image API data:", base_url, NTISAS.tweet_images[base_url]);
dimensions_promise = Promise.resolve(NTISAS.tweet_images[base_url].original_info);
} else {
this.debug('warn', "Missing image API data:", base_url);
dimensions_promise = JSPLib.utility.getImageDimensions(image_url);
}
Promise.all([size_promise, dimensions_promise]).then(([size,dimensions])=>{
NTISAS.image_data[image_url] = Object.assign(dimensions, {size: size});
resolve(NTISAS.image_data[image_url]);
});
});
}
function ReadableBytes(bytes) {
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return JSPLib.utility.setPrecision((bytes / Math.pow(1024, i)), 2) + ' ' + sizes[i];
}
function GetFileExtension(url,splitter=' ') {
let parser = new URL(url);
let pathname = parser.pathname.split(splitter)[0];
let extpos = pathname.lastIndexOf('.');
return pathname.slice(extpos + 1);
}
function GetThumbUrl(url,splitter,ext,size) {
let parser = new URL(url);
let pathname = parser.pathname.split(splitter)[0];
let extpos = pathname.lastIndexOf('.');
return parser.origin + pathname.slice(0, extpos) + `?format=${ext}&name=${size}`;
}
function GetFileURLNameExt(file_url) {
let path_index = file_url.lastIndexOf('/');
let file_ident = file_url.slice(path_index + 1);
let [file_name,extension] = file_ident.split('.');
extension = extension.split(/\W+/)[0];
return [file_name, extension];
}
function GetNormalImageURL(image_url) {
let extension = JSPLib.utility.arrayIntersection(image_url.split(/\W+/), ['jpg', 'png', 'gif']);
for (let i = 0; i < HANDLED_IMAGES.length; i++) {
let match = image_url.match(HANDLED_IMAGES[i].regex);
if (match && extension.length !== 0) {
return JSPLib.utility.sprintf(HANDLED_IMAGES[i].format, ...HANDLED_IMAGES[i].arguments(match, extension));
}
}
for (let i = 0; i < UNHANDLED_IMAGES.length; i++) {
let match = image_url.match(UNHANDLED_IMAGES[i]);
if (match) {
return null;
}
}
return false;
}
function GetNomatchHelp(no_url_results,no_iqdb_results,no_sauce_results) {
let help_info = [NO_MATCH_HELP];
if (no_iqdb_results || no_sauce_results) {
help_info.push(NO_RESULTS_HELP);
}
if (!no_url_results) {
help_info.push(CHECK_URL_HELP);
}
if (!no_iqdb_results) {
help_info.push(CHECK_IQDB_HELP);
}
if (!no_sauce_results) {
help_info.push(CHECK_SAUCE_HELP);
}
return help_info.join('\n');
}
function RemoveDuplicates(obj_array,attribute){
const attribute_index = JSPLib.utility.getObjectAttributes(obj_array, attribute);
return obj_array.filter((obj,index) => (attribute_index.indexOf(obj[attribute]) === index));
}
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);
}
//Auxiliary functions
function GetList(name) {
NTISAS.lists[name] = NTISAS.lists[name] || {};
if (!('list' in NTISAS.lists[name])) {
NTISAS.lists[name].list = GetLocalData('ntisas-' + name, []);
NTISAS.lists[name].list = CorrectStringArray(name, NTISAS.lists[name].list);
}
return NTISAS.lists[name].list;
}
function SaveList(name,list,delay=true) {
NTISAS.lists[name].list = list;
if (delay) {
setTimeout(()=>{
SetLocalData('ntisas-' + name, list);
}, STORAGE_DELAY);
} else {
SetLocalData('ntisas-' + name, list);
}
}
function SavePosts(mapped_posts) {
mapped_posts.forEach((mapped_post)=>{
let expires_duration = PostExpiration(mapped_post.created);
let data_expires = JSPLib.utility.getExpires(expires_duration);
SaveData('post-' + mapped_post.id, {value: mapped_post, expires: data_expires}, 'danbooru');
});
}
function SaveUsers(mapped_users) {
mapped_users.forEach((mapped_user)=>{
let data_expires = JSPLib.utility.getExpires(USER_EXPIRES);
SaveData('user-' + mapped_user.id, {value: mapped_user, expires: data_expires}, 'danbooru');
});
}
function SavePostUsers(mapped_posts) {
let all_users = mapped_posts.map((post)=>({id: post.uploaderid, name: post.uploadername}));
let unique_users = RemoveDuplicates(all_users, 'id');
SaveUsers(unique_users);
}
function PostExpiration(created_timestamp) {
let created_interval = Date.now() - created_timestamp;
if (created_interval < JSPLib.utility.one_day) {
return MIN_POST_EXPIRES;
} else 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 day slots between 1 day and 30 days
let days_month = 30;
return LogarithmicExpiration(day_interval, day_slots, days_month, MAX_POST_EXPIRES);
} else {
return MAX_POST_EXPIRES;
}
}
function SetCheckPostvers() {
if (JSPLib.concurrency.checkTimeout('ntisas-timeout', GetPostVersionsExpiration()) || WasOverflow()) {
clearTimeout(CheckPostvers.timeout);
CheckPostvers.timeout = setTimeout(()=>{
if ((NTISAS.database_info || NTISAS.user_settings.bypass_server_mode) && JSPLib.concurrency.reserveSemaphore(PROGRAM_SHORTCUT, 'postvers')) {
CheckPostvers();
}
}, POST_VERSIONS_CALLBACK);
}
}
async function AddViewCount(tweet_id) {
if (!NTISAS.recorded_views.includes(tweet_id)) {
let views = await GetData('view-' + tweet_id, 'danbooru');
let mapped_view = {
count: (views ? views.value.count : 0) + 1,
viewed: Date.now()
};
let data_expires = JSPLib.utility.getExpires(VIEW_EXPIRES);
SaveData('view-' + tweet_id, {value: mapped_view, expires: data_expires}, 'danbooru', false);
NTISAS.recorded_views.push(tweet_id);
}
}
function UpdateImageDict(tweet) {
let $tweet = $(tweet);
let tweet_id = $tweet.data('tweet-id');
if ((tweet_id in API_DATA.tweets) && ('extended_entities' in API_DATA.tweets[tweet_id]) && ('media' in API_DATA.tweets[tweet_id].extended_entities)) {
API_DATA.tweets[tweet_id].extended_entities.media.forEach((image)=>{
NTISAS.tweet_images[image.media_url_https] = image;
});
}
}
function GetTweetQuartile(tweetid) {
if (tweetid in NTISAS.tweet_finish) {
return NTISAS.tweet_finish[tweetid];
}
let windowsize = NTISAS.user_settings.score_window_size;
let pos = NTISAS.tweet_pos.indexOf(tweetid);
let fave = NTISAS.tweet_faves[pos];
let posmin = Math.max(0, pos - windowsize);
let posmax = Math.min(NTISAS.tweet_pos.length, pos + windowsize);
let subarray = NTISAS.tweet_faves.slice(posmin, posmax);
var quartilepos = Math.floor(subarray.length / 4);
var sortedfaves = subarray.sort((a,b) => (a - b));
var q1 = sortedfaves[quartilepos];
var q2 = sortedfaves[2 * quartilepos];
var q3 = sortedfaves[3 * quartilepos];
var min = Math.min(...sortedfaves);
var max = Math.max(...sortedfaves);
var outlierq1 = (q1 + min) / 2;
var outlierq3 = (q3 + max) / 2;
let quartile = 5;
if (fave >= outlierq3) {
quartile = 0;
} else if (fave >= q3) {
quartile = 1;
} else if (fave >= q2) {
quartile = 2;
} else if (fave >= q1) {
quartile = 3;
} else if (fave >= outlierq1) {
quartile = 4;
}
if ((posmax - posmin) >= (windowsize * 2)) {
NTISAS.tweet_finish[tweetid] = quartile;
}
return quartile;
}
function GetImageLinks(tweet) {
let $obj = $('[data-image-url]', tweet).sort((entrya,entryb)=>($(entrya).data('image-num') - $(entryb).data('image-num')));
return JSPLib.utility.getDOMAttributes($obj, 'image-url');
}
function GetTweetStat(tweet,types) {
for (let i = 0; i < types.length; i++) {
let label = $(`[data-testid=${types[i]}]`, tweet).attr('aria-label');
let match = label && label.match(/\d+/);
if (match) {
return parseInt(match[0]);
}
}
return 0;
}
function SetVideoDownload($download_section,video_url) {
let [video_name,extension] = GetFileURLNameExt(video_url);
let download_filename = JSPLib.utility.regexReplace(NTISAS.filename_prefix, {
ORDER: 'video1',
IMG: video_name
}) + '.' + extension;
$('.ntisas-download-video', $download_section[0])
.attr('href', video_url)
.attr('download', download_filename)
.show();
}
function UpdatePostIDsLink(tweet_id, post_ids) {
let $tweet = $(`[data-tweet-id=${tweet_id}]`);
if ($tweet.length === 0) {
return;
}
let $link_container = $('.ntisas-link-menu', $tweet[0]);
let $link = $('.ntisas-database-match, .ntisas-confirm-save', $tweet[0]);
if ($link.length && NTISAS.user_settings.advanced_tooltips_enabled) {
$link.qtiptisas('destroy', true);
}
if (post_ids.length === 0) {
InitializeNoMatchesLinks(tweet_id, $link_container);
} else {
InitializePostIDsLink(tweet_id, $link_container, $tweet[0], post_ids);
}
}
function PromptSavePostIDs($link,$tweet,tweet_id,$replace,message,initial_post_ids) {
let prompt_string = prompt(message, initial_post_ids.join(', '));
if (prompt_string !== null) {
let confirm_post_ids = JSPLib.utility.arrayUnique(
prompt_string.split(',')
.map(Number)
.filter((num) => JSPLib.validate.validateID(num))
);
this.debug('log', "Confirmed IDs:", confirm_post_ids);
if (confirm_post_ids.length === 0) {
RemoveData('tweet-' + tweet_id, 'twitter');
} else {
SaveData('tweet-' + tweet_id, confirm_post_ids, 'twitter');
}
UpdatePostIDsLink(tweet_id, confirm_post_ids);
NTISAS.channel.postMessage({type: 'postlink', tweet_id: tweet_id, post_ids: confirm_post_ids});
}
}
function GetSelectPostIDs(tweet_id,type) {
if (!NTISAS[type][tweet_id]) {
return [];
}
let $select_previews = $('.ntisas-post-select', NTISAS[type][tweet_id][0]);
return JSPLib.utility.getDOMAttributes($select_previews, 'id', Number);
}
function SetThumbnailWait(container,all_posts) {
all_posts.forEach(async (post)=>{
let blob = await JSPLib.network.getData(post.thumbnail);
let image_blob = blob.slice(0, blob.size, 'image/jpeg');
let blob_url = window.URL.createObjectURL(image_blob);
$(`[data-id=${post.id}] img`, container).attr('src', blob_url);
});
}
//Checks and removes a value from a hash key if it exists
function RemoveHashKeyValue(hash,key,value) {
if ((key in hash) && hash[key].includes(value)) {
hash[key] = JSPLib.utility.arrayDifference(hash[key], [value]);
if (hash[key].length === 0) {
delete hash[key];
}
return true;
}
return false;
}
function ProcessPostvers(postvers) {
postvers.sort((a,b) => (a.id - b.id));
var account_swaps = 0;
var inactive_posts = 0;
var reversed_posts = 0;
var add_entries = {};
var rem_entries = {};
postvers.forEach((postver)=>{
if (postver.source_changed) {
if (postver.version === 1) {
let tweet_id = JSPLib.utility.findAll(postver.source, TWEET_REGEX)[1];
if (tweet_id) {
add_entries[tweet_id] = add_entries[tweet_id] || [];
add_entries[tweet_id] = JSPLib.utility.arrayUnion(add_entries[tweet_id], [postver.post_id]);
}
} else {
let tweet_id = {};
let twitter_add = postver.added_tags.find((tag) => SOURCE_TWITTER_REGEX.test(tag)) || "";
tweet_id.add = JSPLib.utility.findAll(twitter_add, SOURCE_TWITTER_REGEX)[1];
let twitter_rem = postver.removed_tags.find((tag) => SOURCE_TWITTER_REGEX.test(tag)) || "";
tweet_id.rem = JSPLib.utility.findAll(twitter_rem, SOURCE_TWITTER_REGEX)[1];
if (tweet_id.add && tweet_id.rem) {
if (tweet_id.add === tweet_id.rem) {
tweet_id.add = tweet_id.rem = undefined;
account_swaps++;
} else {
this.debug('log', "ID swap detected", tweet_id.rem, "->", tweet_id.add);
}
}
if (tweet_id.add) {
add_entries[tweet_id.add] = add_entries[tweet_id.add] || [];
add_entries[tweet_id.add] = JSPLib.utility.arrayUnion(add_entries[tweet_id.add], [postver.post_id]);
if (RemoveHashKeyValue(rem_entries, tweet_id.add[0], postver.post_id)) {
this.debug('log', "Source delete reversal detected", tweet_id.add);
}
}
if (tweet_id.rem) {
rem_entries[tweet_id.rem] = rem_entries[tweet_id.rem] || [];
JSPLib.utility.arrayUnion(rem_entries[tweet_id.rem], [postver.post_id]);
if (RemoveHashKeyValue(add_entries, tweet_id.rem, postver.post_id)) {
this.debug('log', "Source add reversal detected", tweet_id.rem);
}
}
}
}
if (postver.added_tags.includes('bad_twitter_id') || postver.removed_tags.includes('bad_twitter_id')) {
let tweet_id = JSPLib.utility.findAll(postver.source, TWEET_REGEX)[1];
if (tweet_id) {
if (postver.removed_tags.includes('bad_twitter_id')) {
this.debug('log', "Activated tweet:", tweet_id);
add_entries[tweet_id] = add_entries[tweet_id] || [];
add_entries[tweet_id] = JSPLib.utility.arrayUnion(add_entries[tweet_id], [postver.post_id]);
reversed_posts++;
if (RemoveHashKeyValue(rem_entries, tweet_id, postver.post_id)) {
this.debug('log', "Tweet remove reversal detected", tweet_id);
}
} else if (postver.added_tags.includes('bad_twitter_id')) {
rem_entries[tweet_id] = rem_entries[tweet_id] || [];
rem_entries[tweet_id] = JSPLib.utility.arrayUnion(rem_entries[tweet_id], [postver.post_id]);
inactive_posts++;
if (RemoveHashKeyValue(add_entries, tweet_id, postver.post_id)) {
this.debug('log', "Tweet add reversal detected", tweet_id);
}
}
}
}
});
if (account_swaps > 0) {
this.debug('log', "Account swaps detected:", account_swaps);
}
if (inactive_posts > 0) {
this.debug('log', "Inactive tweets detected:", inactive_posts);
}
if (reversed_posts > 0) {
this.debug('log', "Activated tweets detected:", reversed_posts);
}
return [add_entries, rem_entries];
}
function GetPageType() {
NTISAS.page_match = XRegExp.exec(window.location.href.split('#')[0], PageRegex());
if (!NTISAS.page_match) {
return 'other';
}
switch (NTISAS.page_match.groups.site) {
case NTISAS.page_match.groups.main:
return 'main';
case NTISAS.page_match.groups.media:
return 'media';
case NTISAS.page_match.groups.search:
return 'search';
case NTISAS.page_match.groups.tweet:
return 'tweet';
case NTISAS.page_match.groups.web_tweet:
return 'web_tweet';
case NTISAS.page_match.groups.hashtag:
return 'hashtag';
case NTISAS.page_match.groups.list:
return 'list';
case NTISAS.page_match.groups.home:
return 'home';
case NTISAS.page_match.groups.likes:
return 'likes';
case NTISAS.page_match.groups.replies:
return 'replies';
case NTISAS.page_match.groups.photo:
return 'photo';
case NTISAS.page_match.groups.topics:
return 'topics';
case NTISAS.page_match.groups.moments:
return 'moments';
case NTISAS.page_match.groups.display:
return 'display';
default:
this.debug('warn', "Regex error:", window.location.href, NTISAS.page_match);
return 'default';
}
}
function UpdateHighlightControls() {
if (!NTISAS.user_settings.score_highlights_enabled) {
return;
}
let [user_ident,all_idents] = GetUserIdent();
if (user_ident && IsMediaTimeline()) {
let no_highlight_list = GetList('no-highlight-list');
if (JSPLib.utility.arrayHasIntersection(no_highlight_list, all_idents)) {
NTISAS.artist_highlights_enabled = false;
DisplayControl('enable', HIGHLIGHT_CONTROLS, 'highlights');
$('#ntisas-fade-level-display').hide();
$('#ntisas-hide-level-display').hide();
} else {
NTISAS.artist_highlights_enabled = true;
DisplayControl('disable', HIGHLIGHT_CONTROLS, 'highlights');
$('#ntisas-fade-level-display').show();
$('#ntisas-hide-level-display').show();
}
} else {
NTISAS.artist_highlights_enabled = false;
DisplayControl('unavailable', HIGHLIGHT_CONTROLS, 'highlights');
$('#ntisas-fade-level-display').hide();
$('#ntisas-hide-level-display').hide();
}
}
function UpdateArtistHighlights() {
if (!NTISAS.artist_highlights_enabled) {
$('.ntisas-fade').removeClass('ntisas-fade');
$('.ntisas-hide').removeClass('ntisas-hide');
return;
}
let [user_ident,all_idents] = GetUserIdent();
if (user_ident) {
let no_highlight_list = GetList('no-highlight-list');
let fade_levels = SCORE_LEVELS.slice(NTISAS.fade_level);
let fade_selectors = JSPLib.utility.joinList(fade_levels, '.ntisas-', null, ',');
let hide_levels = SCORE_LEVELS.slice(NTISAS.hide_level);
let hide_selectors = JSPLib.utility.joinList(hide_levels, '.ntisas-', null, ',');
$('.ntisas-fade').removeClass('ntisas-fade');
$('.ntisas-hide').removeClass('ntisas-hide');
if (!JSPLib.utility.arrayHasIntersection(no_highlight_list, all_idents)) {
$(fade_selectors).addClass('ntisas-fade');
$(hide_selectors).addClass('ntisas-hide');
}
}
}
function UpdateViewControls() {
let switch_type = (NTISAS.view_highlights ? 'disable' : 'enable');
DisplayControl(switch_type, VIEW_CONTROLS, 'views');
}
function UpdateIQDBControls() {
if (!NTISAS.user_settings.autoclick_IQDB_enabled) {
return;
}
let [user_ident,all_idents] = GetUserIdent();
if (user_ident && IsMediaTimeline()) {
let auto_iqdb_list = GetList('auto-iqdb-list');
if (JSPLib.utility.arrayHasIntersection(auto_iqdb_list, all_idents)) {
NTISAS.artist_iqdb_enabled = true;
DisplayControl('disable', IQDB_CONTROLS, 'autoiqdb');
} else {
NTISAS.artist_iqdb_enabled = false;
DisplayControl('enable', IQDB_CONTROLS, 'autoiqdb');
}
} else if (IsTweetPage()) {
NTISAS.artist_iqdb_enabled = false;
DisplayControl('active', IQDB_CONTROLS, 'autoiqdb');
} else {
NTISAS.artist_iqdb_enabled = false;
$('#ntisas-unavailable-autoiqdb').show();
DisplayControl('unavailable', IQDB_CONTROLS, 'autoiqdb');
}
}
function DisplayControl(control,all_controls,type) {
let all_selectors = JSPLib.utility.joinList(all_controls, '#ntisas-', '-' + type, ',');
$(all_selectors).hide();
setTimeout(()=>{$(`#ntisas-${control}-${type}`).show();}, JQUERY_DELAY);
}
function UpdateIndicatorControls() {
if (!NTISAS.user_settings.tweet_indicators_enabled) {
return;
}
let indicators_enabled = GetLocalData('ntisas-indicator-controls', true);
if (indicators_enabled) {
DisplayControl('disable', INDICATOR_CONTROLS, 'indicators');
$('.ntisas-footer-entries').show();
$('#ntisas-indicator-counter').show();
} else {
DisplayControl('enable', INDICATOR_CONTROLS, 'indicators');
$('.ntisas-footer-entries').hide();
$('#ntisas-indicator-counter').hide();
}
}
function UpdateTweetIndicators() {
if (!NTISAS.user_settings.tweet_indicators_enabled) {
return;
}
let artist_list = GetList('artist-list');
let tweet_list = GetList('tweet-list');
$('.ntisas-tweet').each((i,entry)=>{ UpdateTweetIndicator(entry, artist_list, tweet_list); });
let indicators_enabled = GetLocalData('ntisas-indicator-controls', true);
if (indicators_enabled) {
$('.ntisas-footer-entries').show();
} else {
$('.ntisas-footer-entries').hide();
}
}
function UpdateTweetIndicator(tweet,artist_list,tweet_list) {
let $tweet = $(tweet);
let [tweet_id,,,user_ident,all_idents] = GetTweetInfo($tweet);
let active_indicators = [];
if (JSPLib.utility.arrayHasIntersection(artist_list, all_idents)) {
active_indicators.push('mark-artist');
}
if (tweet_list.includes(tweet_id)) {
active_indicators.push('mark-tweet');
}
if (NTISAS.counted_artists.includes(user_ident)) {
active_indicators.push('count-artist');
}
if (NTISAS.counted_tweets.includes(tweet_id)) {
active_indicators.push('count-tweet');
}
let shown_indicators = false;
ALL_INDICATOR_TYPES.forEach((type)=>{
if (active_indicators.includes(type)) {
$(`.ntisas-indicators .ntisas-${type}`, tweet).show();
$(`.ntisas-footer-entries .ntisas-${type}`, tweet).addClass('ntisas-activated');
shown_indicators = true;
} else {
$(`.ntisas-indicators .ntisas-${type}`, tweet).hide();
$(`.ntisas-footer-entries .ntisas-${type}`, tweet).removeClass('ntisas-activated');
}
});
if (shown_indicators) {
$('.ntisas-stream-tweet .ntisas-tweet-status', tweet).css('min-height', '1.5em');
} else {
$('.ntisas-stream-tweet .ntisas-tweet-status', tweet).css('min-height', '');
}
}
async function GetAllCurrentRecords() {
let i = 0;
while (true) {
if (!WasOverflow() || !NTISAS.database_info) {
//Main exit condition
break;
}
clearTimeout(CheckPostvers.timeout);
if (JSPLib.concurrency.reserveSemaphore(PROGRAM_SHORTCUT, 'postvers')) {
JSPLib.notice.notice(`Querying Danbooru...[${i}]`, false, false);
await CheckPostvers();
} else {
JSPLib.notice.notice(`Waiting on other tasks to finish...[${i}]`, false, false);
await JSPLib.utility.sleep(POST_VERSIONS_CALLBACK);
}
i++;
}
}
async function PickImage(event,type,pick_func) {
let similar_class = 'ntisas-check-' + type;
let [$link,$tweet,tweet_id,,,,,$replace] = GetEventPreload(event, similar_class);
let all_image_urls = GetImageLinks($tweet[0]);
if (all_image_urls.length === 0) {
this.debug('log', "Images not loaded yet...");
JSPLib.notice.notice("No images detected.");
return false;
}
this.debug('log', "All:", all_image_urls);
if ((all_image_urls.length > 1) && IsQuerySettingEnabled('pick_image', type) && (typeof pick_func !== 'function' || pick_func())) {
if (!NTISAS.tweet_dialog[tweet_id]) {
NTISAS.tweet_dialog[tweet_id] = InitializeConfirmContainer(all_image_urls);
NTISAS.dialog_ancor[tweet_id] = $link;
}
NTISAS.tweet_dialog[tweet_id].dialog('open');
let status = await NTISAS.tweet_dialog[tweet_id].prop('promiseConfirm');
if (!status) {
this.debug('log', "Exiting...");
return false;
}
let selected_indexes = GetSelectPostIDs(tweet_id, 'tweet_dialog');
var selected_image_urls = all_image_urls.filter((image,index) => selected_indexes.includes(index));
} else {
selected_image_urls = all_image_urls;
}
this.debug('log', "Selected:", selected_image_urls);
$link.removeClass(similar_class).html("loading…");
return [$link,$tweet,tweet_id,$replace,selected_image_urls];
}
function ProcessSimilarData(type,tweet_id,$tweet,$replace,selected_image_urls,similar_data,autosave_func) {
let flat_data = similar_data.flat();
if (flat_data.length > 0) {
let max_score = Math.max(...JSPLib.utility.getObjectAttributes(flat_data, 'score'));
let classname = 'ntisas-similar-match-poor';
if (max_score > 95.0) {
classname = 'ntisas-similar-match-great';
} else if (max_score > 90.0) {
classname = 'ntisas-similar-match-good';
} else if (max_score > 85.0) {
classname = 'ntisas-similar-match-fair';
}
let similar_post_ids = JSPLib.utility.arrayUnique(JSPLib.utility.getNestedObjectAttributes(flat_data, ['post', 'id']));
if (IsQuerySettingEnabled('auto_save', type) || ((typeof autosave_func === 'function') && autosave_func())) {
if (NTISAS.merge_results.includes(tweet_id)) {
let merge_ids = GetSessionTwitterData(tweet_id);
similar_post_ids = JSPLib.utility.arrayUnion(merge_ids, similar_post_ids);
}
SaveData('tweet-' + tweet_id, similar_post_ids, 'twitter');
InitializePostIDsLink(tweet_id, $replace, $tweet[0], similar_post_ids);
NTISAS.channel.postMessage({type: 'postlink', tweet_id: tweet_id, post_ids: similar_post_ids});
} else {
$replace.html(RenderSimilarIDsLink(similar_post_ids, flat_data, classname, type));
NTISAS.similar_results[tweet_id] = similar_post_ids;
if (NTISAS.user_settings.advanced_tooltips_enabled) {
let $postlink = $('.ntisas-confirm-save', $tweet[0]);
InitializeQtip($postlink, tweet_id);
//Some elements are delayed in rendering, so render ahead of time
NTISAS.tweet_qtip[tweet_id] = InitializeSimilarContainer(selected_image_urls, similar_data, tweet_id, type);
}
}
} else {
SaveData(type + '-' + tweet_id, {value: true, expires: JSPLib.utility.getExpires(SIMILAR_EXPIRES)}, 'danbooru');
InitializeNoMatchesLinks(tweet_id, $replace);
}
}
function GetTweetInfo($tweet) {
let tweet_id = String($tweet.data('tweet-id'));
let user_id = String($tweet.data('user-id') || "");
let screen_name = String($tweet.data('screen-name'));
let user_ident = user_id || screen_name;
let all_idents = JSPLib.utility.arrayUnique([user_ident, screen_name]);
return [tweet_id, user_id, screen_name, user_ident, all_idents];
}
function GetEventPreload(event,classname) {
let $link = $(event.target);
let $tweet = $link.closest('.ntisas-tweet');
let [tweet_id,user_id,screen_name,user_ident,all_idents] = GetTweetInfo($tweet);
let $replace = $(`[data-tweet-id=${tweet_id}] .${classname}`).parent();
return [$link, $tweet, tweet_id, user_id, screen_name, user_ident, all_idents, $replace];
}
function IsPageType(types) {
return types.includes(NTISAS.page) || (NTISAS.page === 'display' && types.includes(NTISAS.prev_page));
}
function IsTweetPage() {
return IsPageType(['tweet','web_tweet']);
}
function IsMediaTimeline() {
return (NTISAS.page === 'media') || (NTISAS.page === 'search' && NTISAS.account && MEDIA_TYPES.includes(NTISAS.queries.filter));
}
function IsIQDBAutoclick() {
return NTISAS.user_settings.autoclick_IQDB_enabled && ((NTISAS.artist_iqdb_enabled && IsMediaTimeline()) || IsTweetPage());
}
//File functions
function ReadFileAsync(fileselector,is_json) {
const context = this;
return new Promise((resolve,reject)=>{
let files = $(fileselector).prop('files');
if (!files.length) {
alert('Please select a file!');
reject();
return;
}
var file = files[0];
var reader = new FileReader();
reader.onloadend = function(event) {
if (event.target.readyState == FileReader.DONE) {
context.debug('log', "File loaded:", file.size);
let data = event.target.result;
if (is_json) {
try {
data = JSON.parse(data);
} catch (e) {
JSPLib.notice.error("Error: File is not JSON!");
reject();
}
}
resolve(data);
}
};
var blob = file.slice(0, file.size);
reader.readAsBinaryString(blob);
});
}
function DownloadObject(export_obj, export_name, is_json) {
var export_data = export_obj;
var encoding = {type: 'text/plain;charset=utf-8'};
if (is_json) {
export_data = JSON.stringify(export_obj);
encoding = {type: 'text/json;charset=utf-8'};
}
var blob = new Blob([export_data], encoding);
saveAs(blob, export_name);
}
//Render functions
function RenderSideMenu() {
let current_message = (!CheckLocalData('ntisas-recent-timestamp') ? MUST_INSTALL_HELP : UPDATE_RECORDS_HELP);
let fade_level = JSPLib.utility.displayCase(NTISAS.user_settings.score_levels_faded[0]);
let hide_level = JSPLib.utility.displayCase(NTISAS.user_settings.score_levels_hidden[0]);
let current_fade_html = JSPLib.utility.sprintf(FADE_HIGHLIGHT_HTML, fade_level);
let current_hide_html = JSPLib.utility.sprintf(HIDE_HIGHLIGHT_HTML, hide_level);
return JSPLib.utility.regexReplace(SIDE_MENU, {
CURRENTRECORDS: RenderCurrentRecords(),
CURRENTHELP: RenderHelp(current_message),
RECORDSHELP: RenderHelp(REFRESH_RECORDS_HELP),
SAUCEHELP: RenderHelp(AVAILABLE_SAUCE_HELP),
HIGHLIGHTS: HIGHLIGHT_HTML,
HIGHLIGHTSHELP: RenderHelp(HIGHLIGHTS_HELP),
CURRENTFADE: current_fade_html,
CURRENTFADEHELP: RenderHelp(FADE_HIGHLIGHT_HELP),
CURRENTHIDE: current_hide_html,
CURRENTHIDEHELP: RenderHelp(HIDE_HIGHLIGHT_HELP),
VIEWS: VIEWS_HTML,
VIEWSHELP: RenderHelp(VIEWS_HELP),
AUTOCLICKIQDB: AUTO_IQDB_HTML,
AUTOCLICKIQDBHELP: RenderHelp(AUTO_IQDB_HELP),
INDICATOR: INDICATOR_HTML,
INDICATORHELP: RenderHelp(INDICATOR_HELP),
LOCKPAGE: LOCKPAGE_HTML,
LOCKPAGEHELP: RenderHelp(LOCKPAGE_HELP),
ERRORMESSAGES: JSPLib.network.error_messages.length,
ERRORMESSAGESHELP: RenderHelp(ERROR_MESSAGES_HELP),
SETTINGSHELP: SETTINGS_HELP,
STATISTICSHELP: RenderHelp(STATISTICS_HELP),
});
}
function RenderCurrentRecords() {
var record_html = "";
let timestamp = CheckLocalData('ntisas-recent-timestamp');
if (timestamp) {
let timestring = new Date(timestamp).toLocaleString();
let timeagostring = ((Date.now() - timestamp) < GetPostVersionsExpiration() * 2 ? "Up to date" : TimeAgo(timestamp));
record_html = `${timeagostring}`;
} else {
record_html = 'Loading...';
}
return record_html;
}
function RenderDatabaseVersion() {
let timestring = new Date(NTISAS.server_info.timestamp).toLocaleString();
let url = `${NTISAS.domain}/post_versions?page=b${NTISAS.server_info.post_version+1}`;
return `${NTISAS.server_info.post_version}`;
}
function RenderDownloadLinks($tweet,position,is_video) {
let [tweet_id,user_id,screen_name,,] = GetTweetInfo($tweet);
let date_string = GetDateString(Date.now());
let time_string = GetTimeString(Date.now());
NTISAS.filename_prefix = JSPLib.utility.regexReplace(NTISAS.user_settings.filename_prefix_format, {
TWEETID: tweet_id,
USERID: user_id,
USERACCOUNT: screen_name,
DATE: date_string,
TIME: time_string
});
var image_links = GetImageLinks($tweet[0]);
var hrefs = image_links.map((image) => (image + ':orig'));
let html = 'Download Originals ( 0 ) ';
for (let i = 0; i < image_links.length; i++) {
let image_num = i + 1;
let [image_name,extension] = GetFileURLNameExt(image_links[i]);
let download_filename = JSPLib.utility.regexReplace(NTISAS.filename_prefix, {
ORDER: 'img' + String(image_num),
IMG: image_name
}) + '.' + extension;
html += `Image #${image_num}`;
}
if (is_video) {
html += 'Video #1';
}
if (image_links.length > 1) {
html += 'All images';
}
if (position === 'above') {
html = HORIZONTAL_RULE + html;
} else if (position === 'below') {
html += HORIZONTAL_RULE;
}
return `
${html}
`;
}
function RenderPostIDsLink(posts,classname) {
let mergelink = "";
let helpinfo = CONFIRM_DELETE_HELP;
if (NTISAS.user_settings.merge_results_enabled) {
mergelink = ' | Merge';
helpinfo += '\n' + MERGE_RESULTS_HELP;
}
let helplink = RenderHelp(helpinfo);
let post_ids = JSPLib.utility.getObjectAttributes(posts, 'id');
if (posts.length === 1) {
let title = GetLinkTitle(posts[0]);
return `( post #${post_ids[0]}${mergelink} | ${helplink} )`;
} else {
let title = GetMultiLinkTitle(posts);
return `( ${post_ids.length} sources${mergelink} | ${helplink} ) `;
}
}
function RenderSimilarIDsLink(post_ids,similar_data,classname,type) {
let helplink = RenderHelp(CONFIRM_IQDB_HELP);
if (similar_data.length === 1) {
let url = `${NTISAS.domain}/posts/${post_ids[0]}`;
let title = GetLinkTitle(similar_data[0].post);
return `( post #${post_ids[0]} | ${helplink} )`;
} else {
let all_posts = JSPLib.utility.getObjectAttributes(similar_data, 'post');
let unique_posts = RemoveDuplicates(all_posts, 'id');
let url = `${NTISAS.domain}/posts?` + $.param({tags: "status:any id:" + post_ids.join(',')}) + GetCustomQuery();
let title = GetMultiLinkTitle(unique_posts);
return `( ${post_ids.length} sources | ${helplink} )`;
}
}
function RenderAllSimilar(all_iqdb_results,image_urls,type) {
var image_results = [];
var max_results = 0;
all_iqdb_results.forEach((iqdb_results,i)=>{
if (iqdb_results.length === 0) {
return;
}
max_results = Math.max(max_results, iqdb_results.length);
let html = RenderSimilarContainer("Image " + (i + 1), iqdb_results, image_urls[i], i);
image_results.push(html);
});
let render_width = Math.min(((max_results + 1) * BASE_PREVIEW_WIDTH) + BASE_QTIP_WIDTH + 20, 850);
return `
${image_results.join(HORIZONTAL_RULE)}
`;
}
function RenderSimilarContainer(header,iqdb_results,image_url,index) {
var html = RenderTwimgPreview(image_url, index);
html += ``;
iqdb_results.forEach((iqdb_result)=>{
let is_user_upload = iqdb_result.post.uploaderid === NTISAS.user_data.id;
let addons = RenderPreviewAddons(iqdb_result.post.source, null, iqdb_result.score, iqdb_result.post.ext, iqdb_result.post.size, iqdb_result.post.width, iqdb_result.post.height, is_user_upload);
html += RenderPostPreview(iqdb_result.post, addons);
});
return `
${header} (${RenderHelp(IQDB_SELECT_HELP)})
${html}
`;
}
function RenderConfirmContainer(image_urls) {
let html = "";
image_urls.forEach((image,i)=>{
html += RenderTwimgPreview(image, i, true);
});
return `
Selected images will be used for the query. Press Submit to execute query, or Cancel to go back.
${html}
`;
}
function RenderPostsContainer(all_posts) {
let html = "";
all_posts.forEach((post)=>{
let is_user_upload = post.uploaderid === NTISAS.user_data.id;
let addons = RenderPreviewAddons(post.source, post.id, null, post.ext, post.size, post.width, post.height, is_user_upload);
html += RenderPostPreview(post, addons);
});
let delete_all_checked = (NTISAS.user_settings.delete_all_reset ? "checked" : "");
let width_addon = (all_posts.length > 10 ? 'style="width:850px"' : "");
return `
`;
}
//Expects a mapped post as input
function RenderPostPreview(post,append_html="") {
let [width,height] = JSPLib.utility.getPreviewDimensions(post.width, post.height, POST_PREVIEW_DIMENSION);
let padding_height = POST_PREVIEW_DIMENSION - height;
return `
${append_html}
`;
}
function RenderTwimgPreview(image_url,index,selectable) {
let file_type = GetFileExtension(image_url, ':');
let thumb_url = GetThumbUrl(image_url, ':', 'jpg', '360x360');
let image_html = ``;
let selected_class = "";
if (selectable) {
image_html = `${image_html}`;
selected_class = 'ntisas-post-select';
}
let append_html = RenderPreviewAddons('https://twitter.com', null, null, file_type);
return `
${image_html}
${append_html}
`;
}
function RenderPreviewAddons(source,id,score,file_ext,file_size,width,height,is_user_upload=false) {
let title_text = "Original image";
if (JSPLib.validate.validateID(id)) {
title_text = "post #" + id;
} else if (JSPLib.validate.isNumber(score)) {
title_text = `Similarity: ${JSPLib.utility.setPrecision(score, 2)}`;
}
let uploader_addon = (is_user_upload ? 'class="ntisas-post-upload"' : "");
let domain = (source.match(/^https?:\/\//) ? JSPLib.utility.getDomainName(source, 2) : "NON-WEB");
let size_text = (Number.isInteger(file_size) && Number.isInteger(width) && Number.isInteger(height) ? `${ReadableBytes(file_size)} (${width}x${height})` : "");
return `