|
// ==UserScript== |
|
// @name HypnoHub Userscript |
|
// @namespace https://github.com/TankNut |
|
// @version 2.6.5 |
|
// @description Adds some missing functionality back to the hub |
|
// @author TankNut |
|
// @updateURL https://gist.githubusercontent.com/TankNut/1a3b21335044a9cecfcfa603b99a8855/raw/hypnohub-userscript.user.js |
|
// @downloadURL https://gist.githubusercontent.com/TankNut/1a3b21335044a9cecfcfa603b99a8855/raw/hypnohub-userscript.user.js |
|
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js |
|
// @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.3/moment.min.js |
|
// @require https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js |
|
// @require https://userscripts-mirror.org/scripts/source/107941.user.js |
|
// @match https://hypnohub.net/* |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=hypnohub.net |
|
// @grant GM_getValue |
|
// @grant GM_setValue |
|
// @grant GM_listValues |
|
// @grant GM_deleteValue |
|
// @grant GM_addStyle |
|
// @grant GM_xmlhttpRequest |
|
// @grant GM_download |
|
// @connect https://hypnohub.net |
|
// ==/UserScript== |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
const implications = ` |
|
after_sex after_anal after_vaginal |
|
ass hypnotic_ass ass_expansion ass_grab large_ass huge_ass cum_in_ass cum_on_ass |
|
bikini micro_bikini |
|
bottomless nude |
|
breast_grab groping holding_breasts |
|
breasts small_breasts large_breasts huge_breasts hyper_breasts hypnotic_breasts cum_on_breasts breast_expansion breast_sucking breast_grab |
|
collaboration collaborative_fellatio collaborative_paizuri collaborative_handjob collaborative_buttjob collaborative_footjob collaborative_breast_smother |
|
cum cum_in_pussy cum_on_body cum_in_mouth cum_on_face cum_on_breasts cum_in_ass cum_on_hair cum_in_uterus cum_on_clothes cum_in_clothing covered_with_cum cum_on_ass cumming_out_brain cum_drinking cum_in_nose hypnotic_cum cum_on_feet cum_on_hands cum_in_nipples cum_in_ear bukkake |
|
dress dress_lift |
|
feet hypnotic_feet barefoot foot_focus |
|
fellatio collaborative_fellatio double_fellatio fellatio_under_mask autofellatio |
|
footjob collaborative_footjob |
|
futanari futasub futadom futa_only multiple_futa |
|
gloves fingerless_gloves opera_gloves |
|
glowing glowing_eyes |
|
handjob collaborative_handjob double_handjob |
|
magic hypnotic_magic |
|
manip caption |
|
oral fellatio analingus cunnilingus |
|
paizuri double_paizuri collaborative_paizuri |
|
penis small_penis large_penis huge_cock hyper_cock |
|
piercing nipple_piercing navel_piercing tongue_piercing clitoris_piercing penis_piercing nose_piercing lip_piercing nose_ring |
|
pov male_pov female_pov pov_dom pov_sub |
|
robot robot_girl robot_boy robotization |
|
sex vaginal anal missionary doggy_style |
|
sex_toy dildo vibrator anal_beads |
|
skirt skirt_lift |
|
sound voice_acted |
|
standing standing_at_attention |
|
symbol_in_eyes binary_eyes heart_eyes spiral_eyes |
|
tentacles tentacle_sex tentaclejob hypnotic_tentacle |
|
text caption caption_only translated partially_translated translation_request dialogue |
|
tongue tongue_out |
|
topless nude |
|
urination golden_shower |
|
`; |
|
|
|
/* eslint no-unused-vars: 0 */ |
|
const j = $.noConflict(true); |
|
|
|
j.fn.random = function() { |
|
return j(this[Math.floor(Math.random() * this.length)]); |
|
}; |
|
|
|
j.fn.center = function(options) { |
|
options = j.extend({ // Default values |
|
inside:window, // element, center into window |
|
transition: 0, // millisecond, transition time |
|
minX:0, // pixel, minimum left element value |
|
minY:0, // pixel, minimum top element value |
|
withScrolling:false, // booleen, take care of the scrollbar (scrollTop) |
|
vertical:true, // booleen, center vertical |
|
horizontal:true // booleen, center horizontal |
|
}, options); |
|
|
|
return this.each(function() { |
|
const props = {position:'fixed'}; |
|
|
|
if (options.vertical) { |
|
let top = (j(options.inside).height() - j(this).outerHeight()) / 2; |
|
|
|
if (options.withScrolling) { |
|
top += j(options.inside).scrollTop() || 0; |
|
} |
|
|
|
top = (top > options.minY ? top : options.minY); |
|
j.extend(props, {top: top+'px'}); |
|
} |
|
if (options.horizontal) { |
|
let left = (j(options.inside).width() - j(this).outerWidth()) / 2; |
|
|
|
if (options.withScrolling) { |
|
left += j(options.inside).scrollLeft() || 0; |
|
} |
|
|
|
left = (left > options.minX ? left : options.minX); |
|
j.extend(props, {left: left+'px'}); |
|
} |
|
|
|
if (options.transition > 0) { |
|
j(this).animate(props, options.transition); |
|
} else { |
|
j(this).css(props); |
|
} |
|
|
|
return j(this); |
|
}); |
|
} |
|
|
|
function getUrlParameter(param, staticUrl) { |
|
const url = new URL(staticUrl ? staticUrl : window.location, window.location); |
|
|
|
return url.searchParams.get(param) ?? false; |
|
} |
|
|
|
function promisedRequest(details) { |
|
return new Promise((resolve, reject) => { |
|
details.onabort = reject; |
|
details.onerror = reject; |
|
details.ontimeout = reject; |
|
details.onload = resolve; |
|
|
|
GM_xmlhttpRequest(details); |
|
}); |
|
} |
|
|
|
function getRenderedSize(contains, cWidth, cHeight, width, height, pos) { |
|
var oRatio = width / height, |
|
cRatio = cWidth / cHeight; |
|
|
|
return function() { |
|
if (contains ? (oRatio > cRatio) : (oRatio < cRatio)) { |
|
this.width = cWidth; |
|
this.height = cWidth / oRatio; |
|
} else { |
|
this.width = cHeight * oRatio; |
|
this.height = cHeight; |
|
} |
|
this.left = (cWidth - this.width)*(pos/100); |
|
this.right = this.width + this.left; |
|
return this; |
|
}.call({}); |
|
} |
|
|
|
function getImgSizeInfo(img) { |
|
var pos = window.getComputedStyle(img).getPropertyValue('object-position').split(' '); |
|
|
|
return getRenderedSize(true, |
|
img.width, img.height, |
|
img.naturalWidth, img.naturalHeight, |
|
parseInt(pos[0]) |
|
); |
|
} |
|
|
|
function getCookie(key) { |
|
const match = document.cookie.match(new RegExp('(^| )' + key + '=([^;]+)')); |
|
|
|
if (match) { |
|
return match[2]; |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class API { |
|
static get(options) { |
|
const url = new URL('https://hypnohub.net/index.php'); |
|
|
|
options = { |
|
page: 'dapi', |
|
s: 'post', |
|
json: 1, |
|
...options |
|
} |
|
|
|
for (const [option, val] of Object.entries(options)) { |
|
url.searchParams.set(option, val) |
|
} |
|
|
|
return promisedRequest({ |
|
url: url.href, |
|
responseType: options.json == 1 ? 'json' : 'text' |
|
}); |
|
} |
|
|
|
static getPost(id, force) { |
|
return new Promise((resolve) => { |
|
const cache = Cache.get('post_' + id); |
|
|
|
if (cache && !force) { |
|
resolve(cache); |
|
} else { |
|
resolve(this.get({ |
|
s: 'post', |
|
q: 'index', |
|
id: id |
|
}).then(details => { |
|
if (!details.response) { |
|
return false; |
|
} |
|
|
|
const data = details.response[0]; |
|
|
|
Cache.set('post_' + id, { |
|
width: data.width, |
|
height: data.height, |
|
file_url: data.file_url |
|
}, moment.duration(1, 'months')); |
|
|
|
return data; |
|
})); |
|
} |
|
}); |
|
} |
|
|
|
static getSearch(tags, page) { |
|
return this.get({ |
|
s: 'post', |
|
q: 'index', |
|
tags: tags, |
|
pid: page, |
|
limit: 42 |
|
}).then(details => details.response); |
|
} |
|
|
|
static editPost(id, ops) { |
|
return this.get({ |
|
page: 'post', |
|
s: 'view', |
|
id: id, |
|
json: 0 |
|
}).then(details => { |
|
return new Promise(resolve => { |
|
const doc = new DOMParser().parseFromString(details.response, 'text/html'); |
|
const data = j('#edit_form', doc).serializeArray(); |
|
|
|
const get = name => data.find(obj => obj.name == name).value; |
|
const set = (name, val) => data.find(obj => obj.name == name).value = val.toString(); |
|
|
|
set('pconf', 1); |
|
|
|
if (ops.addTags.length > 0 || ops.removeTags.length > 0) { |
|
const tags = Object.fromEntries(get('tags').split(' ').filter(tag => tag.length > 0).map(tag => [tag, true])); |
|
|
|
ops.addTags?.forEach(tag => tags[tag] = true); |
|
ops.removeTags?.forEach(tag => delete tags[tag]); |
|
|
|
set('tags', Object.keys(tags).sort().join(' ')); |
|
} |
|
|
|
if(ops.implications == 'builtin' || ops.implications == 'both') set('tags', Implications.handle(get('tags'), implications)); |
|
if(ops.implications == 'custom' || ops.implications == 'both') set('tags', Implications.handle(get('tags'), Settings.get('tag_implications'))); |
|
|
|
if(ops.rating) set('rating', ops.rating); |
|
if(ops.parent) set('parent', ops.parent); |
|
if(ops.source) set('source', ops.source); |
|
|
|
j.post('./public/edit_post.php', data, resolve); |
|
}); |
|
}); |
|
} |
|
|
|
static getQueue() { |
|
const url = new URL('https://hypnohub.net/admin/moderate/'); |
|
|
|
url.searchParams.set('page', 'post_queue'); |
|
|
|
return promisedRequest({ |
|
url: url.href, |
|
responseType: 'text' |
|
}).then(details => { |
|
const doc = new DOMParser().parseFromString(details.response, 'text/html'); |
|
const count = j('table.highlightable > tbody > tr', doc).length - 1; |
|
|
|
return count; |
|
}); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Cache { |
|
static regex = /cache_.+/; |
|
|
|
static init() { |
|
if (!this.get('initial-setup-done')) { |
|
let imported = false; |
|
|
|
for (const key of GM_listValues()) { |
|
if (Settings.settings[key]) { |
|
imported = true; |
|
Settings.set(key, GM_SuperValue.get(key, Settings.settings[key].fallback)); |
|
} |
|
} |
|
|
|
if (imported) { |
|
Cache.set('version', '1.18.0'); |
|
Notice.add('Your old settings have been successfully transferred over.', {dismissable: true}); |
|
} |
|
|
|
this.set('initial-setup-done', true); |
|
} |
|
|
|
const now = this.time(); |
|
|
|
for (const key of GM_listValues()) { |
|
if (key.match(this.regex)) { |
|
const container = GM_SuperValue.get(key); |
|
|
|
if (typeof container === 'undefined') |
|
continue; |
|
|
|
if (container.expire) { |
|
if (typeof container.expire == 'string') { |
|
GM_deleteValue(key); |
|
} else if (container.lastAccess + container.expire < now) { |
|
GM_deleteValue(key); |
|
} |
|
} |
|
} else { |
|
GM_deleteValue(key); |
|
} |
|
} |
|
} |
|
|
|
static time(time) { |
|
return Math.floor(+(time ?? moment()) / 1000); |
|
} |
|
|
|
static get(key, fallback) { |
|
key = 'cache_' + key; |
|
|
|
const container = GM_SuperValue.get(key); |
|
|
|
if (typeof container === 'undefined') { |
|
return fallback; |
|
} |
|
|
|
if (container.expire) { |
|
container.lastAccess = this.time(); |
|
|
|
GM_SuperValue.set(key, container); |
|
} |
|
|
|
return container.data; |
|
} |
|
|
|
static has(key) { |
|
return typeof GM_SuperValue.get('cache_' + key) !== 'undefined'; |
|
} |
|
|
|
static set(key, data, expire) { |
|
const container = { |
|
data: data |
|
} |
|
|
|
if (expire) { |
|
container.lastAccess = this.time(); |
|
container.expire = this.time(expire); |
|
} |
|
|
|
GM_SuperValue.set('cache_' + key, container); |
|
} |
|
|
|
static delete(key) { |
|
GM_deleteValue('cache_' + key); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class ClickDrag { |
|
static init(element) { |
|
const w = j(window); |
|
|
|
element.css('cursor', 'grab').attr('draggable', false); |
|
|
|
let pos = { x: 0, y: 0 }; |
|
|
|
const mouseDownHandler = function(e) { |
|
if (e.which != 1) return; |
|
|
|
element.css('cursor', 'grabbing'); |
|
|
|
pos = { |
|
x: e.clientX, |
|
y: e.clientY |
|
}; |
|
|
|
w.mousemove(mouseMoveHandler); |
|
w.mouseup(mouseUpHandler); |
|
} |
|
|
|
const mouseMoveHandler = function(e) { |
|
window.scrollBy(pos.x - e.clientX, pos.y - e.clientY); |
|
|
|
pos = { |
|
x: e.clientX, |
|
y: e.clientY |
|
}; |
|
} |
|
|
|
const mouseUpHandler = function() { |
|
w.off('mousemove', mouseMoveHandler); |
|
w.off('mouseup', mouseUpHandler); |
|
|
|
element.css('cursor', 'grab'); |
|
} |
|
|
|
element.mousedown(mouseDownHandler); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Favorite { |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Filter { |
|
static parse(list) { |
|
return list.split('\n').filter(group => group.length > 0) |
|
.map(element => element.split(' ').filter(tag => tag.length > 0)); |
|
} |
|
|
|
static escape(string) { |
|
return string.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string |
|
} |
|
|
|
static colorRegExp = new RegExp('^#[\\da-f]+$', 'i'); |
|
|
|
static match(tags, list) { |
|
let found = false; |
|
|
|
list.some(group => { |
|
let color = '#ff9800'; |
|
const subgroup = [...group]; |
|
|
|
if (this.colorRegExp.test(group[0])) { |
|
color = group[0]; |
|
subgroup.shift(); |
|
} |
|
|
|
if (subgroup.every(tag => { |
|
const inverted = (tag[0] == '~' || tag[0] == '!'); |
|
|
|
if (inverted) { |
|
return !(new RegExp(`(\\s|^)${this.escape(tag.substring(1))}(\\s|$)`, 'g').test(tags)); |
|
} else { |
|
return new RegExp(`(\\s|^)${this.escape(tag)}(\\s|$)`, 'g').test(tags); |
|
} |
|
})) { |
|
found = color |
|
|
|
return true; |
|
} |
|
}); |
|
|
|
return found; |
|
} |
|
|
|
static applyGreylist() { |
|
let greylist = Settings.get('greylist'); |
|
|
|
if (Settings.get('greylist_videos')) { |
|
greylist += '\n#00f video'; |
|
} |
|
|
|
if (Settings.get('greylist_gifs')) { |
|
greylist += '\n#0ff animated_gif'; |
|
} |
|
|
|
greylist = this.parse(greylist); |
|
|
|
const filter = this; |
|
|
|
j('.thumb img').each(function() { |
|
const element = j(this); |
|
const tags = element.attr('title'); |
|
|
|
const match = filter.match(tags, greylist); |
|
|
|
if (match) { |
|
element.css('border', `3px solid ${match}`); |
|
} else { |
|
element.css('border', ''); |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Implications { |
|
static init() { |
|
const setting = Settings.get('tag_implications_mode'); |
|
|
|
j('#edit_form > table > tbody > tr:nth-child(14)').before(`<tr><td> |
|
Tag Implications: |
|
<select id="implications"> |
|
<option ${setting == 'off' ? 'selected' : ''}>Off</option> |
|
<option ${setting == 'builtin' ? 'selected' : ''}>Built-in</option> |
|
<option ${setting == 'custom' ? 'selected' : ''}>Custom</option> |
|
<option ${setting == 'both' ? 'selected' : ''}>Both</option> |
|
</select></td></tr>`); |
|
|
|
j('#edit_form').submit(() => { |
|
const mode = j('#implications').prop('selectedIndex'); |
|
|
|
if (mode == 0) return; |
|
|
|
let tags = j('#tags').val(); |
|
|
|
if (mode == 1 || mode == 3) tags = this.handle(tags, implications); |
|
if (mode == 2 || mode == 3) tags = this.handle(tags, Settings.get('tag_implications')); |
|
|
|
j('#tags').val(tags); |
|
}); |
|
} |
|
|
|
static handle(tags, implications) { |
|
const lookup = Object.fromEntries(tags.split(' ').filter(tag => tag.length > 0).map(tag => [tag, true])); |
|
|
|
let rules = implications.split('\n').filter(row => row.length > 0) |
|
.map(tags => tags.split(' ').filter(tag => tag.length > 0)); |
|
|
|
rules = Object.fromEntries(rules.map(tags => [tags.shift(), tags])); |
|
|
|
for (const [implied, matches] of Object.entries(rules)) { |
|
if (lookup[implied]) continue; |
|
|
|
for (const tag of matches) { |
|
if (lookup[tag]) { |
|
lookup[implied] = true; |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
return Object.keys(lookup).sort().join(' '); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Navbar { |
|
static bar = j('#navbar'); |
|
|
|
static add(content, index) { |
|
if (typeof content == 'string') { |
|
content = j(content); |
|
} |
|
|
|
let insert = index && j(`li:nth-child(${index - 1})`, this.bar); |
|
|
|
if (content.is('li')) { |
|
return index ? content.insertAfter(insert) : content.appendTo(this.bar); |
|
} else { |
|
let li = j('<li></li>'); |
|
|
|
return content.appendTo(index ? li.insertAfter(insert) : li.appendTo(this.bar)); |
|
} |
|
} |
|
|
|
static remove(selector) { |
|
const element = j(selector, this.bar); |
|
|
|
if (element.is('li')) { |
|
element.remove(); |
|
} else { |
|
element.parent().remove(); |
|
} |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class SubNavbar { |
|
static bar = j('#subnavbar'); |
|
|
|
static create() { |
|
this.bar = j('<ul class="flat-list" id="subnavbar" style="margin-bottom: 1px;"></ul>').insertAfter(Navbar.bar); |
|
} |
|
|
|
static add(content, index) { |
|
if (!this.bar.length) this.create(); |
|
|
|
if (typeof content == 'string') { |
|
content = j(content); |
|
} |
|
|
|
let insert = index && j(`li:nth-child(${index - 1})`, this.bar); |
|
|
|
if (content.is('li')) { |
|
return index ? content.insertAfter(insert) : content.appendTo(this.bar); |
|
} else { |
|
let li = j('<li></li>'); |
|
|
|
return content.appendTo(index ? li.insertAfter(insert) : li.appendTo(this.bar)); |
|
} |
|
} |
|
|
|
static remove(selector) { |
|
if (!this.bar.length) return; |
|
|
|
const element = j(selector, this.bar); |
|
|
|
if (element.is('li')) { |
|
element.remove(); |
|
} else { |
|
element.parent().remove(); |
|
} |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Notice { |
|
static element; |
|
|
|
static init() { |
|
if (!getUrlParameter('page')) { |
|
this.element = j('<div id="status-notices"></div>').prependTo('#links'); |
|
} |
|
|
|
if (j('#status-notices').length == 0) { |
|
this.element = j('<div id="status-notices"></div>').prependTo('#content'); |
|
} |
|
|
|
this.element = j('#status-notices'); |
|
|
|
j('br', this.element).remove(); |
|
} |
|
|
|
static get(match) { |
|
const notices = j('.status-notice'); |
|
|
|
for (const notice of notices) { |
|
if (j(`:contains(${match})`, notice).length > 0) { |
|
return notice; |
|
} |
|
} |
|
} |
|
|
|
static add(content, options = {}) { |
|
const notice = j(`<div class="status-notice">${content}</div>`); |
|
|
|
if (options.dismissable) { |
|
notice.append(' '); |
|
j('<a href=#>Dismiss</a>').click(function() { |
|
notice.remove(); |
|
|
|
return false |
|
}).appendTo(notice); |
|
} |
|
|
|
return notice.appendTo(this.element); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Resize { |
|
static auto(image) { |
|
const imageRatio = image.prop('naturalWidth') / image.prop('naturalHeight'); |
|
|
|
let viewportHeight = j(window).height(); |
|
let viewportWidth = j(window).width(); |
|
|
|
viewportWidth -= image.position().left + (viewportWidth * 0.01); |
|
viewportHeight -= viewportHeight * 0.05; |
|
|
|
const screenRatio = viewportWidth / viewportHeight; |
|
|
|
if (imageRatio > screenRatio) { |
|
this.wide(image); |
|
} else { |
|
this.tall(image); |
|
} |
|
} |
|
|
|
static disable(image) { |
|
image.width(''); |
|
image.height(''); |
|
|
|
image.removeAttr('image-mode'); |
|
image.trigger('userscript-resized'); |
|
} |
|
|
|
static toggle(image) { |
|
const attr = image.attr('image-mode'); |
|
|
|
switch (attr) { |
|
case 'wide': |
|
this.tall(image); |
|
break; |
|
case 'tall': |
|
this.wide(image); |
|
break; |
|
default: |
|
this.auto(image); |
|
break; |
|
} |
|
} |
|
|
|
static tall(image) { |
|
const viewportHeight = j(window).height(); |
|
|
|
const imageWidth = image.prop('naturalWidth'); |
|
const imageHeight = image.prop('naturalHeight'); |
|
|
|
const maxHeight = viewportHeight - (viewportHeight * 0.05); |
|
|
|
if (imageHeight > maxHeight) { |
|
const ratio = imageWidth / imageHeight; |
|
|
|
image.width(maxHeight * ratio); |
|
image.height(maxHeight); |
|
} else { |
|
image.width(imageWidth); |
|
image.height(imageHeight); |
|
} |
|
|
|
image.attr('image-mode', 'tall'); |
|
image.trigger('userscript-resized'); |
|
} |
|
|
|
static wide(image) { |
|
const viewportWidth = j(window).width(); |
|
|
|
const imageWidth = image.prop('naturalWidth'); |
|
const imageHeight = image.prop('naturalHeight'); |
|
|
|
const maxWidth = viewportWidth - image.position().left - (viewportWidth * 0.01); |
|
|
|
if (imageWidth > maxWidth) { |
|
const ratio = imageHeight / imageWidth; |
|
|
|
image.width(maxWidth); |
|
image.height(maxWidth * ratio); |
|
} else { |
|
image.width(imageWidth); |
|
image.height(imageHeight); |
|
} |
|
|
|
image.attr('image-mode', 'wide'); |
|
image.trigger('userscript-resized'); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Settings { |
|
static categories = [ |
|
'blacklist', |
|
'greylist', |
|
'moderator' |
|
]; |
|
|
|
static settings = {}; |
|
|
|
static register(id, options) { |
|
this.settings[id] = Object.assign({}, { |
|
name: 'Invalid name', |
|
description: 'Invalid description', |
|
type: 'checkbox', |
|
fallback: false |
|
}, options); |
|
} |
|
|
|
static get(id) { |
|
return Cache.get('setting_' + id, this.settings[id].fallback); |
|
} |
|
|
|
static getMod(id) { |
|
if (!id) { |
|
return this.get('is_moderator'); |
|
} |
|
|
|
return this.get('is_moderator') && this.get(id); |
|
} |
|
|
|
static set(id, val) { |
|
Cache.set('setting_' + id, val); |
|
} |
|
|
|
static buildOptions() { |
|
const list = j('#user-edit > form > table > tbody'); |
|
|
|
list.append('<tr><td><label class="block">Userscript options</td></tr>'); |
|
|
|
const basic = []; |
|
const headers = {}; |
|
|
|
for (const category of this.categories) { |
|
headers[category] = [j(`<tr><td><label class="block">${category[0].toUpperCase() + category.substring(1)} options</td></tr>`), []]; |
|
} |
|
|
|
for (const [id, data] of Object.entries(this.settings)) { |
|
if (data.type == 'none') continue; |
|
|
|
const header = data.category ? headers[data.category] : basic; |
|
|
|
header.push(` |
|
<tr> |
|
<th width="15%"> |
|
<label class="block">${data.name}</label> |
|
<p>${data.description}</p> |
|
</th><td width="85%"> |
|
${this.buildControl(id, data)} |
|
</td> |
|
</tr> |
|
`); |
|
} |
|
|
|
list.append(basic); |
|
|
|
for (const category of this.categories) { |
|
list.append(headers[category]); |
|
} |
|
|
|
window.eval('autocomplete_setup')(); |
|
|
|
if (this.get('blacklist') != '') { |
|
j('#tags').val(this.get('blacklist')); |
|
} |
|
|
|
j('#user-edit > form').submit(() => { |
|
for (const [id, data] of Object.entries(this.settings)) { |
|
if (data.type == 'none') continue; |
|
|
|
this.set(id, this.getInput(id, data)); |
|
} |
|
|
|
this.set('blacklist', j('#tags').val()); |
|
|
|
if (this.get('replace_blacklist')) { |
|
j('#tags').val(''); |
|
} |
|
}); |
|
} |
|
|
|
static buildControl(id, data) { |
|
switch (data.type) { |
|
case 'checkbox': |
|
return `<input type="checkbox" id="${id}" name="${id}" ${this.get(id) ? 'checked' : ''}>`; |
|
case 'tags': |
|
return ` |
|
<div class="awesomplete"> |
|
<div class="awesomplete"> |
|
<textarea cols="80" id="${id}" name="${id}" rows="6" autocomplete="off" aria-autocomplete="list">${this.get(id)}</textarea> |
|
<ul hidden=""></ul> |
|
<span class="visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span> |
|
</div> |
|
<ul hidden=""></ul> |
|
<span class="visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span> |
|
</div> |
|
`; |
|
case 'select': |
|
return ` |
|
<select id="${id}" name="${id}"> |
|
${data.options.map(option => `<option value="${option.value}" ${this.get(id) == option.value ? 'selected' : ''}>${option.name}</option>`).join()} |
|
</select> |
|
` |
|
case 'none': |
|
return; |
|
} |
|
} |
|
|
|
static getInput(id, data) { |
|
switch (data.type) { |
|
case 'checkbox': |
|
return j('#' + id).is(':checked'); |
|
case 'tags': |
|
case 'select': |
|
return j('#' + id).val(); |
|
case 'none': |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Spoilers { |
|
static init() { |
|
j('.spoiler').replaceWith(function() { |
|
return '<div class="spoiler spoiler-active">' + j(this).html() + '</div>'; |
|
}); |
|
|
|
j('.spoiler').each(function() { |
|
const element = j(this); |
|
|
|
j('<a href="#">Spoiler</a>').insertBefore(element).click(function() { |
|
element.toggleClass('spoiler-active'); |
|
|
|
return false; |
|
}); |
|
}); |
|
} |
|
} |
|
|
|
GM_addStyle(` |
|
.spoiler { |
|
color: #dedece !important; |
|
background: #272727 !important; |
|
margin-top: 1em; |
|
padding: 2px; |
|
border: 1px solid #bababa; |
|
} |
|
.spoiler-active { |
|
display: none; |
|
} |
|
`); |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Tags { |
|
static init() { |
|
const list = j('#tag-sidebar'); |
|
|
|
j('li.tag-type-artist', list).each(function() { |
|
const name = j('a:last()', this).text().trim(); |
|
|
|
j(this).append(`<a href="index.php?page=artist&s=list&search=${name}&commit=Search">(A)</a>`); |
|
}); |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class TagScript { |
|
static css = ` |
|
.tagscript { |
|
display: none; |
|
|
|
left: 50%; |
|
top: 50%; |
|
|
|
position: absolute; |
|
z-index: 9; |
|
|
|
background-color: #2A2A2A; |
|
border: 1px solid #AAAA9A; |
|
} |
|
|
|
.tagscript-header { |
|
padding: 10px; |
|
cursor: move; |
|
} |
|
|
|
.tagscript-selected img { |
|
border: 3px dashed red !important; |
|
} |
|
|
|
.tagscript textarea { |
|
border-left: 0px; |
|
border-right: 0px; |
|
resize: none; |
|
} |
|
|
|
.tagscript button { |
|
box-sizing: border-box; |
|
border: 1px solid; |
|
color: #aaaa9a; |
|
background: #333; |
|
width: 99px; |
|
height: 19px; |
|
margin: 2px; |
|
} |
|
|
|
.tagscript button:hover { |
|
background: #666; |
|
}`; |
|
|
|
static enabled = () => Cache.get('tagscript_enabled', false); |
|
|
|
static init() { |
|
GM_addStyle(this.css); |
|
|
|
SubNavbar.add('<a href="#">TagScript</a>').click(function() { |
|
TagScript.toggle(); |
|
|
|
return false; |
|
}); |
|
|
|
j(document.body).append(` |
|
<div id="tagscript" class="tagscript"> |
|
<div class="tagscript-header">TagScript Console (<a href="https://gist.github.com/TankNut/1a3b21335044a9cecfcfa603b99a8855#file-tagscript-md" target="_blank">Help</a>)<span style="float:right"><a id="tagscript-close" href="#">Close</a></span></div> |
|
<textarea id="tagscript-entry" rows="8" cols="42"></textarea> |
|
<button id="tagscript-apply">Apply</button> |
|
<button id="tagscript-save">Save</button> |
|
<button id="tagscript-clear">Deselect</button> |
|
</div> |
|
`); |
|
|
|
const div = j('#tagscript'); |
|
|
|
setTimeout(function() { |
|
div.center() |
|
}, 0) |
|
|
|
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0 |
|
|
|
j('.tagscript-header').mousedown(function(event) { |
|
pos3 = event.clientX; |
|
pos4 = event.clientY; |
|
|
|
j(document).mousemove(function(event) { |
|
pos1 = pos3 - event.clientX; |
|
pos2 = pos4 - event.clientY; |
|
|
|
pos3 = event.clientX; |
|
pos4 = event.clientY; |
|
|
|
const offset = div.offset(); |
|
|
|
div.css('left', offset.left - j(document).scrollLeft() - pos1); |
|
div.css('top', offset.top - j(document).scrollTop() - pos2); |
|
div.css('position', 'fixed'); |
|
|
|
return false; |
|
}); |
|
|
|
j(document).mouseup(function() { |
|
j(this).off('mouseup'); |
|
j(this).off('mousemove'); |
|
}); |
|
|
|
return false; |
|
}); |
|
|
|
j('#tagscript-close').click(function() { |
|
TagScript.toggle(); |
|
|
|
return false; |
|
}); |
|
|
|
j('#tagscript-entry').val(Cache.get('tagscript_text', '')); |
|
|
|
j('#tagscript-apply').click(function() { |
|
const text = j('#tagscript-entry').val() |
|
const ops = TagScript.parse(text); |
|
const promises = []; |
|
|
|
Cache.set('tagscript_text', text); |
|
|
|
j('.tagscript-selected').each(function() { |
|
const element = j(this); |
|
const postId = element.attr('id').substr(1); |
|
|
|
promises.push(API.editPost(postId, ops)) |
|
}); |
|
|
|
Promise.all(promises).then(() => { |
|
window.location.reload(); |
|
}); |
|
}); |
|
|
|
j('#tagscript-save').click(function() { |
|
Cache.set('tagscript_text', j('#tagscript-entry').val()); |
|
}); |
|
|
|
j('#tagscript-clear').click(function() { |
|
j('.tagscript-selected').removeClass('tagscript-selected'); |
|
}); |
|
|
|
j('.thumb').click(function() { |
|
if (TagScript.enabled()) { |
|
const element = j(this); |
|
|
|
element.toggleClass('tagscript-selected'); |
|
|
|
return false; |
|
} |
|
}); |
|
|
|
if (this.enabled()) { |
|
div.show(); |
|
} |
|
} |
|
|
|
static toggle() { |
|
const val = !this.enabled(); |
|
|
|
Cache.set('tagscript_enabled', val); |
|
|
|
if (val) { |
|
j('#tagscript').show(); |
|
} else { |
|
console.log('hide'); |
|
j('#tagscript').hide(); |
|
} |
|
} |
|
|
|
static regex = /(\S)+/g; |
|
|
|
static parse(str) { |
|
const ops = { |
|
addTags: [], |
|
removeTags: [], |
|
implications: Settings.get('tag_implications_mode') |
|
}; |
|
|
|
for (const match of str.matchAll(this.regex)) { |
|
const text = match[0]; |
|
|
|
if (text[0] == '+') { ops.addTags.push(text.substring(1)); continue; } |
|
if (text[0] == '-') { ops.removeTags.push(text.substring(1)); continue; } |
|
|
|
if (text.includes(':')) { |
|
const [op, ...arg] = text.split(':'); |
|
|
|
ops[op] = arg.join(':'); |
|
} else { |
|
ops.addTags.push(text); |
|
} |
|
} |
|
|
|
return ops; |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Timestamp { |
|
static getPreference(date) { |
|
switch (Settings.get('timestamp_format')) { |
|
case 'relative': |
|
return date.fromNow(); |
|
case 'calendar': |
|
return date.calendar(null, { |
|
sameElse: 'MMMM Do, YYYY' |
|
}); |
|
case 'hub': |
|
return date.local().format('ddd, MMM DD YYYY, HH:mm') |
|
} |
|
} |
|
|
|
static update(element, val, format, strict) { |
|
const date = new moment.tz(val, format, strict, 'Europe/Brussels'); |
|
|
|
element.attr('title', date.local().toString()); |
|
element.text(this.getPreference(date)); |
|
} |
|
|
|
static get(val, format, strict) { |
|
const date = new moment.tz(val, format, strict, 'Europe/Brussels'); |
|
|
|
return { |
|
title: date.local().toString(), |
|
text: this.getPreference(date) |
|
} |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Userscript { |
|
static handlers = {}; |
|
static globals = []; |
|
|
|
static page; |
|
static mode; |
|
|
|
static register(page, mode, func, css) { |
|
this.handlers[page] = this.handlers[page] || {}; |
|
this.handlers[page][mode] = {func: func, css: css}; |
|
} |
|
|
|
static global(func) { |
|
this.globals.push(func); |
|
} |
|
|
|
static init() { |
|
this.page = getUrlParameter('page'); |
|
this.mode = getUrlParameter('s'); |
|
} |
|
|
|
static run() { |
|
const data = this.handlers?.[this.page]?.[this.mode]; |
|
|
|
if (data) { |
|
data.func && data.func(); |
|
data.css && GM_addStyle(data.css); |
|
} |
|
|
|
for (const callback of this.globals) { |
|
callback(); |
|
} |
|
} |
|
} |
|
|
|
GM_addStyle(` |
|
.has-mail { |
|
border-color: #ee8887 !important; |
|
background: none !important; |
|
} |
|
|
|
#site-title a:not(:first-child) { |
|
background-image: none !important; |
|
} |
|
|
|
.tag-search { |
|
width: 200px !important; |
|
} |
|
`); |
|
|
|
// eslint-disable-next-line no-unused-vars |
|
class Version { |
|
static check(version) { |
|
const saved = Cache.get('version', false); |
|
|
|
Cache.set('version', version); |
|
|
|
if (!saved) { |
|
Notice.add('The hub userscript has been successfully installed.', {dismissable: true}); |
|
} else if (version != saved) { |
|
Notice.add(`The hub userscript has been updated to version ${version}.`, {dismissable: true}); |
|
} |
|
|
|
if (!Userscript.page) { |
|
j('#static-index > div:nth-child(8) > p').append(`<br>Running hub userscript version ${version}`); |
|
} |
|
} |
|
} |
|
|
|
Settings.register('is_moderator', { |
|
name: 'Moderator', |
|
description: 'Required before any of the other options in this category work.', |
|
category: 'moderator' |
|
}); |
|
|
|
Settings.register('admin_panel_visible', { |
|
name: 'Admin panel visibility', |
|
description: 'Whether or not the admin panel link should be visible regardless of where you are on the site.', |
|
category: 'moderator' |
|
}); |
|
|
|
Settings.register('admin_show_summary', { |
|
name: 'Moderation summary', |
|
description: 'Whether viewing the post index should add a summary of the mod queue to the navigation bar.', |
|
category: 'moderator' |
|
}) |
|
|
|
Settings.register('unhide_children', { |
|
name: 'Unhide child posts', |
|
description: 'Redirects you away from the main page where child posts are hidden by default.' |
|
}); |
|
|
|
Settings.register('show_footers', { |
|
name: 'Show post footers', |
|
description: 'Adds an element below each post that shows the rating, score, resolution and provides a direct link to the image itself.', |
|
fallback: true |
|
}); |
|
|
|
Settings.register('tag_implications', { |
|
name: 'Tag implications', |
|
description: 'When using the custom tag implication option the first tag per line will be added if any of the others are present.', |
|
type: 'tags', |
|
fallback: '' |
|
}); |
|
|
|
Settings.register('tag_implications_mode', { |
|
name: 'Default mode', |
|
description: 'Which tag implication mode should be auto-selected when editing a post.', |
|
type: 'select', |
|
options: [ |
|
{name: 'Off', value: 'off'}, |
|
{name: 'Built-in', value: 'builtin'}, |
|
{name: 'Custom', value: 'custom'}, |
|
{name: 'Both', value: 'both'} |
|
], |
|
fallback: 'off' |
|
}); |
|
|
|
Settings.register('timestamp_format', { |
|
name: 'Timestamp format', |
|
description: 'The format to use for timestamps throughout the site.', |
|
type: 'select', |
|
options: [ |
|
{name: 'Relative (x hours ago)', value: 'relative'}, |
|
{name: 'Calendar (Today at HH:MM)', value: 'calendar'}, |
|
{name: 'Hub forums (Mon, Jan 01 2023, HH:MM)', value: 'hub'} |
|
], |
|
fallback: 'relative' |
|
}); |
|
|
|
Settings.register('blacklist', { |
|
type: 'none', |
|
fallback: '' |
|
}); |
|
|
|
Settings.register('replace_blacklist', { |
|
name: 'Replace blacklist', |
|
description: "Replaces the blacklist's filter with the same system used for greylists.", |
|
category: 'blacklist' |
|
}); |
|
|
|
Settings.register('enforce_blacklist', { |
|
name: 'Enforce blacklist', |
|
description: 'Removes the blacklist toggle from any pages that have it.', |
|
category: 'blacklist' |
|
}); |
|
|
|
Settings.register('greylist', { |
|
name: 'Tag greylist', |
|
description: 'Any post matching all rules on a line will get marked with a colored border. Supports #hex color and inverted (! or ~) tags.', |
|
type: 'tags', |
|
fallback: '', |
|
category: 'greylist' |
|
}); |
|
|
|
Settings.register('greylist_videos', { |
|
name: 'Identify videos', |
|
description: 'Gives videos a blue border.', |
|
fallback: true, |
|
category: 'greylist' |
|
}); |
|
|
|
Settings.register('greylist_gifs', { |
|
name: 'Identify gifs', |
|
description: 'Gives animated gifs a cyan border.', |
|
fallback: true, |
|
category: 'greylist' |
|
}); |
|
|
|
Userscript.register('account', 'options', function() { |
|
Settings.buildOptions(); |
|
}); |
|
|
|
Userscript.register('artist', 'list', function() { |
|
const results = j('#content table tbody tr').length - 1; |
|
|
|
if (results == 1) { |
|
window.location.replace(j('#content table tbody tr:nth-child(2) td:nth-child(2) a').attr('href')); |
|
} |
|
}); |
|
|
|
Userscript.register('comment', 'list', function() { |
|
j('div.response-list div.post').each(function() { |
|
const element = j(this); |
|
|
|
if (element.css('display') == 'none') { |
|
element.parents('div[id^=p]').addClass('blacklisted-image'); |
|
} |
|
}); |
|
|
|
if (Settings.get('enforce_blacklist')) { |
|
j('#ci').remove(); |
|
} else { |
|
j('#ci').click(function() { |
|
j('.blacklisted-image, .unblacklisted-image').each(function() { |
|
j(this).toggleClass('blacklisted-image').toggleClass('unblacklisted-image'); |
|
}); |
|
|
|
return false; |
|
}); |
|
} |
|
|
|
j('span.date').each(function() { |
|
const element = j(this); |
|
|
|
Timestamp.update(element, element.text(), ' YYYY-MM-DD HH:mm:ss', true); |
|
}); |
|
|
|
const blacklist = Settings.get('replace_blacklist') ? Filter.parse(Settings.get('blacklist')) : false; |
|
|
|
if (blacklist) { |
|
const posts = window.eval('posts'); |
|
|
|
let blacklistCount = 0; |
|
|
|
for (const [postId, tags] of Object.entries(posts.tags)) { |
|
if (Filter.match(tags, blacklist)) { |
|
j(`#p${postId}`).addClass('blacklisted-image'); |
|
|
|
blacklistCount++; |
|
} |
|
} |
|
|
|
if (blacklistCount > 0) { |
|
j('#ci').text(`(${blacklistCount} hidden)`); |
|
} |
|
} |
|
}, `.blacklisted-image { |
|
display: none; |
|
} |
|
`); |
|
|
|
Userscript.register('favorites', 'view', function() { |
|
if (getUrlParameter('random')) { |
|
window.location = j('span.thumb > a:nth-child(1)').random().attr('href'); |
|
|
|
return; |
|
} |
|
|
|
SubNavbar.add('<a href="#">Download page</a>').click(function() { |
|
j('span.thumb > a:nth-child(1)').each(function() { |
|
const element = j(this); |
|
const id = element.attr('id').substr(1); |
|
|
|
API.getPost(id).then(data => { |
|
const extension = data.file_url.split('.').pop(); |
|
|
|
GM_download(data.file_url, `${id}.${extension}`); |
|
}); |
|
}); |
|
|
|
return false; |
|
}); |
|
|
|
const regex = /pid=(\d+)/; |
|
const click = j('#paginator > a').last().attr('onclick')?.toString(); |
|
const maxPid = click?.match(regex)[1]; |
|
|
|
SubNavbar.add('<a href="#">Random post</a>').click(function() { |
|
if (!maxPid) { |
|
window.location = j('span.thumb > a:nth-child(1)').random().attr('href'); |
|
|
|
return false; |
|
} |
|
|
|
const max = Math.floor(maxPid / 50); |
|
const index = Math.floor(Math.random() * (max + 1)); |
|
|
|
const url = new URL(window.location); |
|
|
|
url.searchParams.set('pid', index * 50); |
|
url.searchParams.set('random', 1); |
|
|
|
window.location = url.href; |
|
|
|
return false; |
|
}); |
|
|
|
Filter.applyGreylist(); |
|
|
|
j('<input type="text" style="width:40px; margin-right:5px" placeholder="page" id="pagePicker"></input>').appendTo('#paginator'); |
|
j('<input type="submit"></input>').appendTo('#paginator').click(function() { |
|
const num = Number(j('#pagePicker').val()); |
|
const url = new URL(window.location); |
|
|
|
url.searchParams.set('pid', Math.max(num * 50 - 50, 0)); |
|
|
|
window.location = url.href; |
|
|
|
return false; |
|
}); |
|
}); |
|
|
|
Userscript.register('forum', 'list', function() { |
|
function get(id) { |
|
return Cache.get('forum_' + id, -1); |
|
} |
|
|
|
function set(id, replies) { |
|
Cache.set('forum_' + id, replies); |
|
} |
|
|
|
SubNavbar.add('<a href="#">Mark as read</a>').click(function() { |
|
j('#forum table.highlightable tbody tr').each(function() { |
|
const id = getUrlParameter('id', j('.forum-topic a', this).attr('href')); |
|
const replies = j('td:nth-child(5)', this).text(); |
|
|
|
set(id, replies); |
|
}); |
|
|
|
j('.forum-topic').removeClass('unread-topic'); |
|
|
|
return false; |
|
}); |
|
|
|
j('#forum table.highlightable tbody tr').each(function() { |
|
const link = j('.forum-topic a', this); |
|
const topic = j('.forum-topic', this); |
|
const locked = j('span.locked-topic', this); |
|
|
|
if (locked.length) { |
|
link.css('color', 'brown'); |
|
locked.remove(); |
|
} |
|
|
|
const id = getUrlParameter('id', link.attr('href')); |
|
|
|
const replies = j('td:nth-child(5)', this).text(); |
|
const storedReplies = get(id); |
|
|
|
let unread = false; |
|
|
|
if (storedReplies < replies) { |
|
topic.addClass('unread-topic'); |
|
unread = true; |
|
} else { |
|
topic.removeClass('unread-topic'); |
|
} |
|
|
|
link.click(() => set(id, replies)); |
|
|
|
const pages = Math.floor(replies / 15); |
|
const title = j('td:nth-child(1)', this); |
|
|
|
title.append(` <a style="color: #666" href="?page=forum&s=view&id=${id}&pid=${pages * 15}">(Last page)</a>`) |
|
.click(() => set(id, replies)); |
|
|
|
if (unread) { |
|
const link = j('<a style="color: #666" href="#">(Mark read)</a>').click(function() { |
|
set(id, replies); |
|
|
|
topic.removeClass('unread-topic'); |
|
j(this).remove(); |
|
|
|
return false |
|
}); |
|
|
|
title.append(' ', link); |
|
} |
|
|
|
const updated = j('td:nth-child(3) span', this); |
|
|
|
Timestamp.update(updated, updated.attr('title'), "ddd, MMM DD 'YY, HH:mm"); |
|
}); |
|
}); |
|
|
|
Userscript.register('forum', 'view', function() { |
|
j('#forum div.post').each(function() { |
|
const element = j('div.author span.date', this); |
|
|
|
Timestamp.update(element, element.text(), 'MM/DD/YY hh:mma', true); |
|
}); |
|
}); |
|
|
|
Userscript.global(function() { |
|
if (Settings.getMod('admin_panel_visible')) { |
|
SubNavbar.remove('a[href="admin/"]'); |
|
|
|
Navbar.add('<a id="admin" href="admin/">Administration Panel</a>'); |
|
} else { |
|
j('a[href="admin/"]').replaceWith('<a id="admin" href="admin/">Administration Panel</a>'); |
|
} |
|
}) |
|
|
|
GM_addStyle(` |
|
#admin { |
|
color: #33cfff; |
|
font-weight: bold; |
|
} |
|
|
|
#admin:hover { |
|
color: #80e1ff; |
|
} |
|
`) |
|
|
|
Userscript.register('pool', 'list', function() { |
|
const activePool = Cache.get('active_pool', -1); |
|
|
|
if (activePool > -1) { |
|
SubNavbar.add('<a href="#">Clear active pool</a>').click(function() { |
|
Cache.delete('active_pool'); |
|
|
|
j(this).remove(); |
|
|
|
return false; |
|
}); |
|
} |
|
}); |
|
|
|
Userscript.register('pool', 'order', function() { |
|
const autoButton = j('#content > form > table > tfoot > tr > td > input[type=button]:nth-child(2)'); |
|
const reverseButton = j('#content > form > table > tfoot > tr > td > input[type=button]:nth-child(3)'); |
|
|
|
autoButton[0].removeAttribute('onclick'); |
|
reverseButton[0].removeAttribute('onclick'); |
|
|
|
autoButton.click(function() { |
|
const interval = parseFloat(prompt('What interval to use', 1)); |
|
|
|
j('.pp').each(function(index) { |
|
j(this).val(index * interval); |
|
}); |
|
|
|
return false; |
|
}); |
|
|
|
reverseButton.click(function() { |
|
const posts = j('.pp'); |
|
const values = []; |
|
|
|
posts.each(function(index) { |
|
values[index] = j(this).val(); |
|
}); |
|
|
|
const offset = values.length - 1; |
|
|
|
posts.each(function(index) { |
|
j(this).val(values[offset - index]); |
|
}); |
|
|
|
return false; |
|
}); |
|
|
|
const uploadButton = j('<input type="button" value="Upload Order">').click(function() { |
|
const posts = j('.pp'); |
|
const values = []; |
|
|
|
posts.each(function(index) { |
|
values[index] = [parseInt(j(this).attr('name').match(/\d+/g)[0]), parseInt(j(this).val())]; |
|
}); |
|
|
|
values.sort((a, b) => a[0] - b[0]); |
|
|
|
for (const index in values) { |
|
j(posts[index]).val(values[index][1]); |
|
} |
|
|
|
return false; |
|
}); |
|
|
|
reverseButton.after(' ', uploadButton); |
|
}); |
|
|
|
Userscript.register('pool', 'show', function() { |
|
const id = getUrlParameter('id'); |
|
const name = j('#pool-show h4').text().slice(6); |
|
|
|
const posts = j('#pool-show div > span.thumb').map(function() { |
|
return j(this).attr('id').slice(1); |
|
}).toArray(); |
|
|
|
Cache.set('pool_' + id, {name: name, posts: posts}, moment.duration(1, 'weeks')); |
|
|
|
SubNavbar.add('<a href="#">Download</a>').click(function() { |
|
j('span.thumb').each(function(i) { |
|
const element = j(this); |
|
const postId = element.attr('id').substr(1); |
|
|
|
API.getPost(postId).then(data => { |
|
const extension = data.file_url.split('.').pop(); |
|
|
|
GM_download(data.file_url, `${id}-${i + 1}-${postId}.${extension}`); |
|
}); |
|
}); |
|
|
|
return false; |
|
}); |
|
|
|
const activePool = Cache.get('active_pool', -1); |
|
|
|
SubNavbar.add(`<a href="#">${activePool == id ? 'Clear active pool' : 'Set active pool'}</a>`).click(function() { |
|
const match = activePool == id; |
|
|
|
if (match) { |
|
Cache.delete('active_pool'); |
|
} else { |
|
Cache.set('active_pool', id); |
|
} |
|
|
|
j(this).text(match ? 'Set active pool' : 'Unset active pool'); |
|
|
|
return false; |
|
}); |
|
|
|
TagScript.init(); |
|
}); |
|
|
|
Userscript.register('post', 'add', function() { |
|
j('#upload-form input[type=file]').change(function() { |
|
const file = this.files[0]; |
|
const objectUrl = URL.createObjectURL(file); |
|
|
|
const img = new Image(); |
|
|
|
img.onload = function() { |
|
if (this.width >= 3200 || this.height >= 2400) { |
|
const tags = j('#tags'); |
|
|
|
tags.val('absurdres ' + tags.val()); |
|
} |
|
|
|
URL.revokeObjectURL(objectUrl); |
|
}; |
|
|
|
img.src = objectUrl; |
|
}) |
|
}); |
|
|
|
Userscript.register('post', 'list', function() { |
|
if (getUrlParameter('random')) { |
|
window.location = j('span.thumb > a:nth-child(1)').random().attr('href'); |
|
|
|
return; |
|
} |
|
|
|
if (Settings.getMod('admin_show_summary')) { |
|
API.getQueue().then(count => { |
|
const val = count == 25 ? '24+' : count.toString(); |
|
|
|
Navbar.add(`<a id="queue" href="admin/moderate/?page=post_queue">Queue: ${val}</a>`); |
|
}); |
|
} |
|
|
|
j('#s123459271093').remove(); |
|
|
|
j('.blacklisted-image > a').each(function() { |
|
j(this).css('display', ''); |
|
}); |
|
|
|
if (Settings.get('enforce_blacklist')) { |
|
j('#blacklisted-sidebar').remove(); |
|
} else { |
|
j('#blacklisted-sidebar > h5 > a').attr('onclick', null).click(() => { |
|
j('.blacklisted-image, .unblacklisted-image').each(function() { |
|
j(this).toggleClass('blacklisted-image').toggleClass('unblacklisted-image'); |
|
}); |
|
|
|
return false; |
|
}) |
|
} |
|
|
|
const blacklist = Settings.get('replace_blacklist') ? Filter.parse(Settings.get('blacklist')) : false; |
|
let blacklistCount = 0; |
|
|
|
const showFooters = Settings.get('show_footers'); |
|
|
|
j('.thumb').each(function() { |
|
const element = j(this); |
|
const id = element.attr('id').substr(1); |
|
|
|
// Not sure what this is |
|
if (id === '1942169814381') { |
|
return |
|
} |
|
|
|
const tags = j('img', this).attr('title'); |
|
|
|
if (showFooters) { |
|
const rating = tags.match(/rating:(\w+)/)[1].toLowerCase(); |
|
const score = tags.match(/score:(\d+)/)[1]; |
|
|
|
const footer = j(`<a class="image-footer"></a>`); |
|
|
|
API.getPost(id).then((data) => { |
|
footer.addClass(rating); |
|
|
|
if (data == false) { |
|
footer.addClass('unapproved'); |
|
footer.text('UNAPPROVED'); |
|
|
|
return |
|
} |
|
|
|
footer.text(`Score: ${score}`); |
|
footer.attr('data-alt', `${data.width} x ${data.height}`); |
|
footer.attr('href', data.file_url); |
|
|
|
footer.hover(function() { |
|
const oldText = footer.text(); |
|
const newText = footer.attr('data-alt'); |
|
|
|
footer.attr('data-alt', oldText); |
|
footer.text(newText); |
|
}); |
|
}); |
|
|
|
j(this).append(footer); |
|
} |
|
|
|
if (blacklist && Filter.match(tags, blacklist)) { |
|
element.addClass('blacklisted-image'); |
|
blacklistCount++; |
|
} |
|
}); |
|
|
|
if (blacklistCount > 0) { |
|
j('#blacklisted-sidebar').show(); |
|
j('#blacklist-count').text(blacklistCount); |
|
} else if (blacklist) { |
|
j('#blacklisted-sidebar').remove(); |
|
} |
|
|
|
Filter.applyGreylist(); |
|
|
|
const options = Cache.get('popular-options', {}); |
|
const enabled = () => Cache.get('popular-search', false); |
|
|
|
j('.tag-search').append(` |
|
<div id="popular-settings"> |
|
<ul> |
|
<li><h5>Popularity options</h5></li> |
|
<li><label>Pages <input id="popular-from" type="number" value="${options.from ?? 0}"> to <input id="popular-to" type="number" value="${options.to ?? 10}"></label></li> |
|
<li><label>Search mode <select id="popular-mode" style="width:auto;"> |
|
<option ${options.mode == 0 ? 'selected' : ''}>All posts</option> |
|
<option ${options.mode == 1 ? 'selected' : ''}>Search only</option> |
|
</select></label></li> |
|
<li><button id="popular-previous" class="popular-button">Previous</button><button id="popular-next" class="popular-button">Next</button></li> |
|
</ul> |
|
</div> |
|
`); |
|
|
|
SubNavbar.add('<a id="toggle-popular" href="#">Toggle Popular Search</a>').click(function() { |
|
const val = !enabled(); |
|
|
|
Cache.set('popular-search', val); |
|
|
|
if (val) { |
|
j('#popular-settings').show(); |
|
} else { |
|
j('#popular-settings').hide(); |
|
} |
|
|
|
return false; |
|
}); |
|
|
|
const tags = getUrlParameter('tags'); |
|
|
|
if (enabled()) { |
|
if (tags != 'all') { |
|
j('.tag-search input[type="text"]').val(Cache.get('last-search', '')); |
|
} |
|
} else { |
|
j('#popular-settings').hide(); |
|
} |
|
|
|
j('#popular-previous').click(() => { |
|
const from = Number(j('#popular-from').val()); |
|
const to = Number(j('#popular-to').val()); |
|
|
|
const offset = (to - from) + 1 |
|
|
|
j('#popular-from').val(Math.max(from - offset, 0)); |
|
j('#popular-to').val(Math.max(to - offset, 0)); |
|
|
|
j('div.tag-search > form').submit(); |
|
|
|
return false; |
|
}); |
|
|
|
j('#popular-next').click(() => { |
|
const from = Number(j('#popular-from').val()); |
|
const to = Number(j('#popular-to').val()); |
|
|
|
const offset = (to - from) + 1 |
|
|
|
j('#popular-from').val(from + offset); |
|
j('#popular-to').val(to + offset); |
|
|
|
j('div.tag-search > form').submit(); |
|
|
|
return false; |
|
}); |
|
|
|
j('div.tag-search > form').submit(() => { |
|
if (enabled()) { |
|
const tags = j('input[name="tags"]').first().val().trim(); |
|
const from = j('#popular-from').val(); |
|
const to = j('#popular-to').val(); |
|
const mode = j('#popular-mode').prop('selectedIndex'); |
|
|
|
Cache.set('last-search', tags); |
|
Cache.set('popular-options', { |
|
from: from, |
|
to: to, |
|
mode: mode |
|
}); |
|
|
|
const search = mode == 0 ? '' : tags; |
|
|
|
const promise1 = from > 0 ? API.getSearch(search, from - 1).then(result => result?.[0]?.id ?? 0) : 0; |
|
const promise2 = to > 0 ? API.getSearch(search, to - 1).then(result => result?.[result.length - 1]?.id ?? 0) : 0; |
|
|
|
Promise.all([promise1, promise2]).then(results => { |
|
const search = `${tags}${results[0] > 0 ? ' id:<='+results[0] : ''}${results[1] > 0 ? ' id:>='+results[1] : ''} sort:score`; |
|
|
|
location.href = `https://hypnohub.net/index.php?page=post&s=list&tags=${search.replaceAll(' ', '+')}`; |
|
}); |
|
|
|
return false; |
|
} |
|
}); |
|
|
|
const regex = /pid=(\d+)/; |
|
|
|
SubNavbar.add('<a href="#">Random (Search)</a>', 6).click(function() { |
|
const click = j('#paginator > div > a').last().attr('href')?.toString(); |
|
|
|
if (!click) { |
|
window.location = j('span.thumb > a:nth-child(1)').random().attr('href'); |
|
return false; |
|
} |
|
|
|
const pid = click.match(regex)[1]; |
|
|
|
if (pid) { |
|
const min = 0; |
|
const max = Math.floor(pid / 42); |
|
const index = Math.floor(Math.random() * (max - min + 1) + min); |
|
|
|
const url = new URL(window.location); |
|
|
|
url.searchParams.set('pid', index * 42); |
|
url.searchParams.set('random', 1); |
|
|
|
window.location = url.href; |
|
} |
|
|
|
return false; |
|
}) |
|
|
|
TagScript.init(); |
|
}, `.blacklisted-image { |
|
display: none; |
|
} |
|
|
|
.unblacklisted-image img { |
|
border: 3px solid #ff0000 !important; |
|
} |
|
|
|
.image-footer { |
|
width: 100%; |
|
height: 17px; |
|
position: absolute; |
|
bottom: 0px; |
|
display: block; |
|
background: #111; |
|
} |
|
|
|
.safe { |
|
color: #3dad3d !important; |
|
} |
|
|
|
.questionable { |
|
color: #adad3d !important; |
|
} |
|
|
|
.explicit { |
|
color: #ad3d3d !important; |
|
} |
|
|
|
.unapproved { |
|
background: #000 !important; |
|
} |
|
|
|
.thumb { |
|
width: 200px; |
|
height: 215px; |
|
position: relative; |
|
} |
|
|
|
.thumb img { |
|
box-sizing: border-box; |
|
-moz-box-sizing: border-box; |
|
-webkit-box-sizing: border-box; |
|
max-height: 200px; |
|
} |
|
|
|
#admin { |
|
color: #33cfff; |
|
font-weight: bold; |
|
} |
|
|
|
#admin:hover { |
|
color: #80e1ff; |
|
} |
|
|
|
div.tag-search input[type=submit]:hover { |
|
background: #666; |
|
} |
|
|
|
button.popular-search { |
|
box-sizing: border-box; |
|
border: 1px solid; |
|
color: #aaaa9a; |
|
background: #333; |
|
width: 100%; |
|
height: 19px; |
|
margin-top: 3px; |
|
} |
|
|
|
button.popular-search:hover { |
|
background: #666; |
|
} |
|
|
|
#toggle-popular { |
|
font-weight: bold; |
|
} |
|
|
|
#popular-settings { |
|
margin-top: 0.2em; |
|
} |
|
|
|
#popular-settings li { |
|
margin-bottom: 2px; |
|
} |
|
|
|
#popular-settings select, |
|
#popular-settings input { |
|
padding: 0; |
|
box-sizing: border-box; |
|
border: #aaaa9a 1px solid; |
|
width: 40px; |
|
} |
|
|
|
#popular-settings label { |
|
width: 200px; |
|
display: inline-block; |
|
} |
|
|
|
#popular-settings ul { |
|
margin-bottom: 0px; |
|
} |
|
|
|
input::-webkit-inner-spin-button { |
|
-webkit-appearance: none; |
|
margin: 0; |
|
} |
|
|
|
input[type=number] { |
|
-moz-appearance: textfield; |
|
} |
|
|
|
button.popular-button { |
|
box-sizing: border-box; |
|
border: 1px solid; |
|
color: #aaaa9a; |
|
background: #333; |
|
width: 99px; |
|
height: 19px; |
|
margin-top: 3px; |
|
margin-left: 1px; |
|
} |
|
|
|
button.popular-button:hover { |
|
background: #666; |
|
} |
|
|
|
#queue { |
|
color: #FF7F50; |
|
font-weight: bold; |
|
} |
|
|
|
#queue:hover { |
|
color: #FFA483; |
|
} |
|
`); |
|
|
|
Userscript.register('post', 'view', function() { |
|
const image = j('#image'); |
|
const imageOptions = j('#post-view > div.sidebar > div:nth-child(5) > ul'); |
|
|
|
// Image resizing |
|
if (image.length) { |
|
image.on('userscript-resized', function() { |
|
const ratio = j(this).width() / window.eval('image').width; |
|
|
|
for (const note of window.eval('Note.all')) { |
|
// Firemonkey fix |
|
if (exportFunction) { |
|
exportFunction(() => ratio, note); |
|
} else { |
|
note.ratio = () => ratio; |
|
} |
|
|
|
for (const p in note.fullsize) { |
|
note.elements.box.style[p] = note.fullsize[p] * ratio + 'px'; |
|
} |
|
} |
|
}).dblclick(() => Resize.toggle(image)).click(event => { |
|
if (event.ctrlKey) { |
|
Resize.disable(image) |
|
} |
|
}); |
|
|
|
if (image[0].complete) { |
|
Resize.auto(image); |
|
} else { |
|
image.on('load', () => Resize.auto(image)); |
|
} |
|
|
|
j('li > a', imageOptions).each(function() { |
|
const element = j(this); |
|
|
|
if (element.text().includes('Original image')) |
|
element.removeAttr('onclick'); |
|
}); |
|
|
|
ClickDrag.init(image); |
|
} |
|
|
|
// Pool notice |
|
const postId = getUrlParameter('id'); |
|
let poolId; |
|
|
|
if (Cache.has('post_pool_id')) { |
|
j('.status-notice[id^=pool]').remove(); |
|
|
|
poolId = Cache.get('post_pool_id'); |
|
|
|
const url = new URL(window.location); |
|
|
|
url.searchParams.set('pool_id', poolId); |
|
|
|
history.replaceState(null, '', url); |
|
|
|
Cache.delete('post_pool_id'); |
|
} else { |
|
poolId = getUrlParameter('pool_id'); |
|
} |
|
|
|
if (poolId) { |
|
const data = Cache.get('pool_' + poolId, false); |
|
|
|
if (!data) { |
|
Notice.add(`This is a post belonging to <a href="index.php?page=pool&s=show&id=${poolId}">pool #${poolId}</a>. Please visit the pool's page so we can discover more about it.`); |
|
} else { |
|
const index = data.posts.indexOf(postId); |
|
|
|
const endIndex = data.posts.length - 1; |
|
|
|
let previous = ''; |
|
let next = ''; |
|
|
|
if (index > 0) { |
|
previous = `<a href="index.php?page=post&s=view&id=${data.posts[index - 1]}&pool_id=${poolId}">« Previous</a> `; |
|
} |
|
|
|
if (index < data.posts.length - 1) { |
|
next = `<a href="index.php?page=post&s=view&id=${data.posts[index + 1]}&pool_id=${poolId}">Next »</a> `; |
|
} |
|
|
|
Notice.add(`${previous}${next}This is post #${index + 1} in the <a href="index.php?page=pool&s=show&id=${poolId}">${data.name}</a> pool.`); |
|
|
|
document.addEventListener('keydown', function(e) { |
|
if (e.target != document.body) return; |
|
|
|
let newIndex = -1; |
|
|
|
if (e.code == 'ArrowLeft' && index > 0) { |
|
newIndex = e.ctrlKey ? 0 : index - 1; |
|
} else if (e.code == 'ArrowRight' && index < endIndex) { |
|
newIndex = e.ctrlKey ? endIndex : index + 1; |
|
} |
|
|
|
if (newIndex != -1) { |
|
window.location.assign(`index.php?page=post&s=view&id=${data.posts[newIndex]}&pool_id=${poolId}`); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
Implications.init(); |
|
|
|
// Moderator approval |
|
if (Settings.getMod()) { |
|
const notice = Notice.get('This post is awaiting moderator approval.'); |
|
|
|
j('<br><br>').appendTo(notice); |
|
j('<b>Mod actions: <a href=# style="color:green">Approve</a></b>').click(function() { |
|
promisedRequest({ |
|
url: `https://hypnohub.net/admin/moderate/index.php?page=post_queue&action=approve&ajax=1&id=${postId}`, |
|
responseType: 'text' |
|
}).then(() => window.location.reload()); |
|
|
|
return false; |
|
}).appendTo(notice); |
|
j('<b> or </b>').appendTo(notice); |
|
j('<b><a href=# style="color:red">Deny</a></b>').click(function() { |
|
const reason = prompt('Reason for deleting this post'); |
|
|
|
promisedRequest({ |
|
url: `https://hypnohub.net/admin/moderate/index.php?page=post_queue&action=deny&ajax=1&id=${postId}&reason=${reason}`, |
|
responseType: 'text' |
|
}).then(() => window.history.back()); |
|
|
|
return false; |
|
}).appendTo(notice); |
|
} |
|
|
|
// Tag editor |
|
let tagEditorActive = false; |
|
|
|
const header = j('.image-sublinks').append(' | '); |
|
|
|
j('<a href=#>Tag Cleaner</a>').click(function() { |
|
if (tagEditorActive) { |
|
const removeList = []; |
|
|
|
j('.tag-editor:checked').each(function() { |
|
removeList.push(j(this).attr('tag')); |
|
}); |
|
|
|
if (removeList.length < 1) { |
|
j('.tag-editor').remove(); |
|
tagEditorActive = false; |
|
|
|
return false; |
|
} |
|
|
|
const tagEntry = j('#tags'); |
|
|
|
tagEntry.text(tagEntry.text().split(' ').filter(tag => tag.length > 0 && !removeList.includes(tag)).join(' ')); |
|
|
|
j('#edit_form input[type="submit"]').click(); // #edit_form.submit() didn't work for some reason |
|
} else { |
|
j('li.tag').each(function() { |
|
j(`<input type=checkbox class=tag-editor tag=${j('a:nth-child(2)', this).text().replaceAll(' ', '_')}>`).prependTo(this); |
|
}); |
|
|
|
tagEditorActive = true; |
|
} |
|
|
|
return false; |
|
}).appendTo(header); |
|
|
|
// Add to pool |
|
j('<li></li>').appendTo(imageOptions).append('<a href="#">Add to pool</a>').click(function() { |
|
const id = prompt('What pool should this post be added to?'); |
|
|
|
if (!id) { |
|
return; |
|
} |
|
|
|
const form = j(`<form action="index.php?page=pool&s=import&id=${id}" method="post"> |
|
<input id="id" name="id" type="hidden" value="${id}"> |
|
<input id="posts_${postId}" name="posts[${postId}]" type="hidden" value="0"> |
|
</form>`).appendTo(document.body); |
|
|
|
j('<input name="commit" type="submit" value="Import">').appendTo(form).click(); |
|
|
|
return false; |
|
}); |
|
|
|
const activePool = Cache.get('active_pool', -1); |
|
|
|
if (activePool > -1) { |
|
j('<li></li>').appendTo(imageOptions).append('<a href="#">Quick add to pool</a>').click(function() { |
|
const form = j(`<form action="index.php?page=pool&s=import&id=${activePool}" method="post"> |
|
<input id="id" name="id" type="hidden" value="${activePool}"> |
|
<input id="posts_${postId}" name="posts[${postId}]" type="hidden" value="0"> |
|
</form>`).appendTo(document.body); |
|
|
|
j('<input name="commit" type="submit" value="Import">').appendTo(form).click(); |
|
|
|
return false; |
|
}); |
|
} |
|
|
|
// Update timestamps |
|
j('div.comment-right-col > div:nth-child(1) > b').each(function() { |
|
const html = j(this).html().split('<br>'); |
|
const timestamp = Timestamp.get(html[0], 'YYYY-MM-DD HH:mm:ss'); |
|
|
|
html[0] = `<span title="${timestamp.title}">\nPosted ${timestamp.text} `; |
|
|
|
j(this).html(html.join('<br>')); |
|
}); |
|
|
|
// My favorites |
|
SubNavbar.add(`<a href="index.php?page=favorites&s=view&id=${getCookie('user_id')}">My Favorites</a>`, 4); |
|
|
|
// Preserve pool_id |
|
j('#edit_form').submit(() => { |
|
const parent = j('#edit_form input[name=parent]'); |
|
|
|
if (parent.val().length == 0) { |
|
parent.val(0); |
|
} |
|
|
|
if (poolId) { |
|
Cache.set('post_pool_id', poolId); |
|
} |
|
}); |
|
|
|
// Post timestamp |
|
{ |
|
const posted = j('#stats > ul > li:nth-child(2)'); |
|
const split = posted.html().split('<br>'); |
|
const timestamp = Timestamp.get(split[0], 'YYYY-MM-DD HH:mm:ss'); |
|
|
|
split[0] = ` Posted: ${timestamp.text}` |
|
|
|
posted.html(split.join('<br>')).attr('title', timestamp.title); |
|
} |
|
|
|
// Blacklisted tags |
|
{ |
|
let blacklist = Settings.get('blacklist'); |
|
|
|
if (!Settings.get('replace_blacklist')) { |
|
blacklist = blacklist.replaceAll(/\s/g, '\n'); |
|
} |
|
|
|
blacklist = Filter.parse(blacklist); |
|
|
|
j('li.tag > a:nth-child(2)').each(function() { |
|
const element = j(this); |
|
|
|
if (Filter.match(element.text().replaceAll(' ', '_'), blacklist)) { |
|
element.addClass('blacklisted'); |
|
} |
|
}); |
|
} |
|
}, `#edit_form input[type="text"] { |
|
display: block; |
|
} |
|
|
|
.blacklisted { |
|
text-decoration: line-through !important; |
|
} |
|
`); |
|
|
|
Userscript.register('wiki', 'list', function() { |
|
const list = j('#post-list > div.content > table > tbody'); |
|
|
|
if (list.children().length == 1) { |
|
window.location.replace(j('tr > td:nth-child(2) > a', list).attr('href')); |
|
|
|
return; |
|
} |
|
|
|
j('#post-list > div.content > table > tbody > tr > td:nth-child(2) > a').each(function() { |
|
const link = j(this); |
|
const tag = link.text(); |
|
|
|
if (!tag.includes(':')) { |
|
link.after(` <a style="color:#666" href="index.php?page=post&s=list&tags=${link.text()}">(search)</a>`); |
|
} |
|
}) |
|
}); |
|
|
|
Userscript.register('wiki', 'view', function() { |
|
j('#content a').each(function() { |
|
const link = j(this); |
|
const url = new URL(link.attr('href'), document.location); |
|
|
|
if (url.searchParams.get('page') == 'wiki' && url.searchParams.get('s') == 'list') { |
|
const tag = url.searchParams.get('search'); |
|
|
|
if (!tag.includes(':')) { |
|
link.after(` <a style="color:#666" href="index.php?page=post&s=list&tags=${tag.replaceAll(' ', '_')}">(search)</a>`); |
|
} |
|
} |
|
}); |
|
}); |
|
|
|
if (Settings.get('unhide_children')) { |
|
const matches = [ |
|
'https://hypnohub.net/index.php?page=post&s=list&tags=all', |
|
'https://hypnohub.net/index.php?page=post&s=list' |
|
]; |
|
|
|
if (matches.includes(window.location.href)) { |
|
window.location.assign('https://hypnohub.net/index.php?page=post&s=list&tags=%20'); |
|
} |
|
|
|
j('#navbar > li:nth-child(2) > a').attr('href', 'https://hypnohub.net/index.php?page=post&s=list&tags=%20'); |
|
} |
|
|
|
(function() { |
|
'use strict'; |
|
|
|
Userscript.init(); |
|
|
|
Notice.init(); |
|
Cache.init(); |
|
Spoilers.init(); |
|
Tags.init(); |
|
|
|
Version.check(GM_info.script.version); |
|
|
|
Userscript.run(); |
|
})(); |
You should add a @updateURL to the userscript for easy updating
Yours should be:
@updateURL https://gist.githubusercontent.com/TankNut/1a3b21335044a9cecfcfa603b99a8855/raw/hypnohub-userscript.user.js