Greasy Fork

Greasy Fork is available in English.

4chan sounds player

Play that faggy music weeb boi

当前为 2020-05-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         4chan sounds player
// @version      0.2.1
// @namespace    rccom
// @description  Play that faggy music weeb boi
// @author       RCC
// @match        *://boards.4chan.org/*
// @match        *://boards.4channel.org/*
// @grant        GM.getValue
// @grant        GM.setValue
// @run-at       document-start
// ==/UserScript==

/**
 * @license
 * Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
 * Build: `lodash include="template,get,set" --production`
 */
;(function(){function t(t,e,r){switch(r.length){case 0:return t.call(e);case 1:return t.call(e,r[0]);case 2:return t.call(e,r[0],r[1]);case 3:return t.call(e,r[0],r[1],r[2])}return t.apply(e,r)}function e(t,e){for(var r=-1,n=null==t?0:t.length,o=Array(n);++r<n;)o[r]=e(t[r],r,t);return o}function r(t){return function(e){return t(e)}}function n(t,r){return e(r,function(e){return t[e]})}function o(t){return"\\"+ft[t]}function u(t,e){return function(r){return t(e(r))}}function c(){}function i(t){var e=-1,r=null==t?0:t.length;
for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function a(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function l(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function f(t,e){var r=Ht(t),n=!r&&Gt(t),o=!r&&!n&&Jt(t),u=!r&&!n&&!o&&Kt(t);if(r=r||n||o||u){for(var n=t.length,c=String,i=-1,a=Array(n);++i<n;)a[i]=c(i);n=a}else n=[];var l,c=n.length;for(l in t)!e&&!St.call(t,l)||r&&("length"==l||o&&("offset"==l||"parent"==l)||u&&("buffer"==l||"byteLength"==l||"byteOffset"==l)||A(l,c))||n.push(l);
return n}function s(t,e,r){var n=t[e];St.call(t,e)&&F(n,r)&&(r!==V||e in t)||_(t,e,r)}function p(t,e){for(var r=t.length;r--;)if(F(t[r][0],e))return r;return-1}function _(t,e,r){"__proto__"==e&&Lt?Lt(t,e,{configurable:true,enumerable:true,value:r,writable:true}):t[e]=r}function h(t){if(null==t)return t===V?"[object Undefined]":"[object Null]";if(Ut&&Ut in Object(t)){var e=St.call(t,Ut),r=t[Ut];try{t[Ut]=V;var n=true}catch(t){}var o=xt.call(t);n&&(e?t[Ut]=r:delete t[Ut]),t=o}else t=xt.call(t);return t}function b(t){
return P(t)&&"[object Arguments]"==h(t)}function y(t){return P(t)&&U(t.length)&&!!at[h(t)]}function g(t,e){return qt(x(t,e,W),t+"")}function j(t){if(typeof t=="string")return t;if(Ht(t))return e(t,j)+"";if(B(t))return Wt?Wt.call(t):"";var r=t+"";return"0"==r&&1/t==-G?"-0":r}function v(t,e){if(Ht(t))return t;var r;return Ht(t)?r=false:(r=typeof t,r=!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=t&&!B(t))||(tt.test(t)||!Z.test(t)||null!=e&&t in Object(e))),r?[t]:Vt(C(t))}function d(t,e,r,n){return t===V||F(t,mt[r])&&!St.call(n,r)?e:t;
}function m(t,e){var r=t.__data__,n=typeof e;return("string"==n||"number"==n||"symbol"==n||"boolean"==n?"__proto__"!==e:null===e)?r[typeof e=="string"?"string":"hash"]:r.map}function O(t,e){var r,n=null==t?V:t[e];return r=!(!L(n)||wt&&wt in n)&&(I(n)?Et:ot).test(E(n)),r?n:V}function A(t,e){var r=typeof t;return e=null==e?9007199254740991:e,!!e&&("number"==r||"symbol"!=r&&ut.test(t))&&-1<t&&0==t%1&&t<e}function S(t,e,r){if(!L(r))return false;var n=typeof e;return!!("number"==n?k(r)&&A(e,r.length):"string"==n&&e in r)&&F(r[e],t);
}function w(t){var e=t&&t.constructor;return t===(typeof e=="function"&&e.prototype||mt)}function x(e,r,n){return r=Bt(r===V?e.length-1:r,0),function(){for(var o=arguments,u=-1,c=Bt(o.length-r,0),i=Array(c);++u<c;)i[u]=o[r+u];for(u=-1,c=Array(r+1);++u<r;)c[u]=o[u];return c[r]=n(i),t(e,this,c)}}function $(t){if(typeof t=="string"||B(t))return t;var e=t+"";return"0"==e&&1/t==-G?"-0":e}function E(t){if(null!=t){try{return At.call(t)}catch(t){}return t+""}return""}function z(t,e){if(typeof t!="function"||null!=e&&typeof e!="function")throw new TypeError("Expected a function");
var r=function(){var n=arguments,o=e?e.apply(this,n):n[0],u=r.cache;return u.has(o)?u.get(o):(n=t.apply(this,n),r.cache=u.set(o,n)||u,n)};return r.cache=new(z.Cache||l),r}function F(t,e){return t===e||t!==t&&e!==e}function k(t){return null!=t&&U(t.length)&&!I(t)}function R(t){if(!P(t))return false;var e=h(t);return"[object Error]"==e||"[object DOMException]"==e||typeof t.message=="string"&&typeof t.name=="string"&&!T(t)}function I(t){return!!L(t)&&(t=h(t),"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t);
}function U(t){return typeof t=="number"&&-1<t&&0==t%1&&9007199254740991>=t}function L(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function P(t){return null!=t&&typeof t=="object"}function T(t){return!(!P(t)||"[object Object]"!=h(t))&&(t=kt(t),null===t||(t=St.call(t,"constructor")&&t.constructor,typeof t=="function"&&t instanceof t&&At.call(t)==$t))}function B(t){return typeof t=="symbol"||P(t)&&"[object Symbol]"==h(t)}function C(t){return null==t?"":j(t)}function M(t){if(k(t))t=f(t);else if(w(t)){
var e,r=[];for(e in Object(t))St.call(t,e)&&"constructor"!=e&&r.push(e);t=r}else t=Tt(t);return t}function D(t){if(k(t))t=f(t,true);else if(L(t)){var e,r=w(t),n=[];for(e in t)("constructor"!=e||!r&&St.call(t,e))&&n.push(e);t=n}else{if(e=[],null!=t)for(r in Object(t))e.push(r);t=e}return t}function N(t){return function(){return t}}function W(t){return t}function q(){return false}var V,G=1/0,H=/\b__p\+='';/g,J=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,Q=/[&<>"']/g,X=RegExp(Q.source),Y=/<%=([\s\S]+?)%>/g,Z=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,tt=/^\w*$/,et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,rt=/\\(\\)?/g,nt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,ot=/^\[object .+?Constructor\]$/,ut=/^(?:0|[1-9]\d*)$/,ct=/($^)/,it=/['\n\r\u2028\u2029\\]/g,at={};
at["[object Float32Array]"]=at["[object Float64Array]"]=at["[object Int8Array]"]=at["[object Int16Array]"]=at["[object Int32Array]"]=at["[object Uint8Array]"]=at["[object Uint8ClampedArray]"]=at["[object Uint16Array]"]=at["[object Uint32Array]"]=true,at["[object Arguments]"]=at["[object Array]"]=at["[object ArrayBuffer]"]=at["[object Boolean]"]=at["[object DataView]"]=at["[object Date]"]=at["[object Error]"]=at["[object Function]"]=at["[object Map]"]=at["[object Number]"]=at["[object Object]"]=at["[object RegExp]"]=at["[object Set]"]=at["[object String]"]=at["[object WeakMap]"]=false;
var lt,ft={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},st=typeof global=="object"&&global&&global.Object===Object&&global,pt=typeof self=="object"&&self&&self.Object===Object&&self,_t=st||pt||Function("return this")(),ht=typeof exports=="object"&&exports&&!exports.nodeType&&exports,bt=ht&&typeof module=="object"&&module&&!module.nodeType&&module,yt=bt&&bt.exports===ht,gt=yt&&st.process;t:{try{lt=gt&&gt.binding&&gt.binding("util");break t}catch(t){}lt=void 0}var jt=lt&&lt.isTypedArray,vt=function(t){
return function(e){return null==t?V:t[e]}}({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),dt=Array.prototype,mt=Object.prototype,Ot=_t["__core-js_shared__"],At=Function.prototype.toString,St=mt.hasOwnProperty,wt=function(){var t=/[^.]+$/.exec(Ot&&Ot.keys&&Ot.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),xt=mt.toString,$t=At.call(Object),Et=RegExp("^"+At.call(St).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),zt=yt?_t.Buffer:V,Ft=_t.Symbol,kt=u(Object.getPrototypeOf,Object),Rt=mt.propertyIsEnumerable,It=dt.splice,Ut=Ft?Ft.toStringTag:V,Lt=function(){
try{var t=O(Object,"defineProperty");return t({},"",{}),t}catch(t){}}(),Pt=zt?zt.isBuffer:V,Tt=u(Object.keys,Object),Bt=Math.max,Ct=Date.now,Mt=O(_t,"Map"),Dt=O(Object,"create"),Nt=Ft?Ft.prototype:V,Wt=Nt?Nt.toString:V;c.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:Y,variable:"",imports:{_:c}},i.prototype.clear=function(){this.__data__=Dt?Dt(null):{},this.size=0},i.prototype.delete=function(t){return t=this.has(t)&&delete this.__data__[t],this.size-=t?1:0,t},
i.prototype.get=function(t){var e=this.__data__;return Dt?(t=e[t],"__lodash_hash_undefined__"===t?V:t):St.call(e,t)?e[t]:V},i.prototype.has=function(t){var e=this.__data__;return Dt?e[t]!==V:St.call(e,t)},i.prototype.set=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Dt&&e===V?"__lodash_hash_undefined__":e,this},a.prototype.clear=function(){this.__data__=[],this.size=0},a.prototype.delete=function(t){var e=this.__data__;return t=p(e,t),!(0>t)&&(t==e.length-1?e.pop():It.call(e,t,1),
--this.size,true)},a.prototype.get=function(t){var e=this.__data__;return t=p(e,t),0>t?V:e[t][1]},a.prototype.has=function(t){return-1<p(this.__data__,t)},a.prototype.set=function(t,e){var r=this.__data__,n=p(r,t);return 0>n?(++this.size,r.push([t,e])):r[n][1]=e,this},l.prototype.clear=function(){this.size=0,this.__data__={hash:new i,map:new(Mt||a),string:new i}},l.prototype.delete=function(t){return t=m(this,t).delete(t),this.size-=t?1:0,t},l.prototype.get=function(t){return m(this,t).get(t)},l.prototype.has=function(t){
return m(this,t).has(t)},l.prototype.set=function(t,e){var r=m(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this};var qt=function(t){var e=0,r=0;return function(){var n=Ct(),o=16-(n-r);if(r=n,0<o){if(800<=++e)return arguments[0]}else e=0;return t.apply(V,arguments)}}(Lt?function(t,e){return Lt(t,"toString",{configurable:true,enumerable:false,value:N(e),writable:true})}:W),Vt=function(t){t=z(t,function(t){return 500===e.size&&e.clear(),t});var e=t.cache;return t}(function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),
t.replace(et,function(t,r,n,o){e.push(n?o.replace(rt,"$1"):r||t)}),e});z.Cache=l;var Gt=b(function(){return arguments}())?b:function(t){return P(t)&&St.call(t,"callee")&&!Rt.call(t,"callee")},Ht=Array.isArray,Jt=Pt||q,Kt=jt?r(jt):y,Qt=function(t){return g(function(e,r){var n=-1,o=r.length,u=1<o?r[o-1]:V,c=2<o?r[2]:V,u=3<t.length&&typeof u=="function"?(o--,u):V;for(c&&S(r[0],r[1],c)&&(u=3>o?V:u,o=1),e=Object(e);++n<o;)(c=r[n])&&t(e,c,n,u);return e})}(function(t,e,r,n){r=D(e);var o=!t;t||(t={});for(var u=-1,c=r.length;++u<c;){
var i=r[u],a=n?n(t[i],e[i],i,t,e):V;a===V&&(a=e[i]),o?_(t,i,a):s(t,i,a)}}),Xt=g(function(e,r){try{return t(e,V,r)}catch(t){return R(t)?t:Error(t)}});c.assignInWith=Qt,c.constant=N,c.keys=M,c.keysIn=D,c.memoize=z,c.set=function(t,e,r){if(null!=t&&L(t)){e=v(e,t);for(var n=-1,o=e.length,u=o-1,c=t;null!=c&&++n<o;){var i=$(e[n]),a=r;if(n!=u){var l=c[i],a=V;a===V&&(a=L(l)?l:A(e[n+1])?[]:{})}s(c,i,a),c=c[i]}}return t},c.extendWith=Qt,c.attempt=Xt,c.eq=F,c.escape=function(t){return(t=C(t))&&X.test(t)?t.replace(Q,vt):t;
},c.get=function(t,e,r){if(null==t)t=V;else{e=v(e,t);for(var n=0,o=e.length;null!=t&&n<o;)t=t[$(e[n++])];t=n&&n==o?t:V}return t===V?r:t},c.identity=W,c.isArguments=Gt,c.isArray=Ht,c.isArrayLike=k,c.isBuffer=Jt,c.isError=R,c.isFunction=I,c.isLength=U,c.isObject=L,c.isObjectLike=P,c.isPlainObject=T,c.isSymbol=B,c.isTypedArray=Kt,c.stubFalse=q,c.template=function(t,e,r){var u=c.templateSettings;r&&S(t,e,r)&&(e=V),t=C(t),e=Qt({},e,u,d),r=Qt({},e.imports,u.imports,d);var i,a,l=M(r),f=n(r,l),s=0;r=e.interpolate||ct;
var p="__p+='",_="sourceURL"in e?"//# sourceURL="+e.sourceURL+"\n":"";if(t.replace(RegExp((e.escape||ct).source+"|"+r.source+"|"+(r===Y?nt:ct).source+"|"+(e.evaluate||ct).source+"|$","g"),function(e,r,n,u,c,l){return n||(n=u),p+=t.slice(s,l).replace(it,o),r&&(i=true,p+="'+__e("+r+")+'"),c&&(a=true,p+="';"+c+";\n__p+='"),n&&(p+="'+((__t=("+n+"))==null?'':__t)+'"),s=l+e.length,e}),p+="';",(e=e.variable)||(p="with(obj){"+p+"}"),p=(a?p.replace(H,""):p).replace(J,"$1").replace(K,"$1;"),p="function("+(e||"obj")+"){"+(e?"":"obj||(obj={});")+"var __t,__p=''"+(i?",__e=_.escape":"")+(a?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+p+"return __p}",
e=Xt(function(){return Function(l,_+"return "+p).apply(V,f)}),e.source=p,R(e))throw e;return e},c.toString=C,c.VERSION="4.17.5",typeof define=="function"&&typeof define.amd=="object"&&define.amd?(_t._=c, define(function(){return c})):bt?((bt.exports=c)._=c,ht._=c):_t._=c}).call(this);
(function() {
	'use strict';

	let isChanX;

	const _ = self._;

	const ns = 'fc-sounds';

	function _logError(message, type = 'error') {
		document.dispatchEvent(new CustomEvent("CreateNotification", {
			bubbles: true,
			detail: {
				type: type,
				content: message,
				lifetime: 5
			}
		}));
	}

	const settingsConfig = [
	{
		property: 'shuffle',
		default: false
	},
	{
		property: 'repeat',
		default: 'all'
	},
	{
		property: 'playlist',
		default: true
	},
	{
		property: 'autoshow',
		default: true,
		title: 'Autoshow',
		description: 'Automatically show the player when the thread contains sounds.',
		showInSettings: true
	},
	{
		property: 'pauseOnHide',
		default: true,
		title: 'Pause on hide',
		description: 'Pause the player when it\'s hidden.',
		showInSettings: true
	},
	{
		property: 'allow',
		default: [
			'4cdn.org',
			'catbox.moe',
			'dmca.gripe',
			'lewd.se',
			'pomf.cat',
			'zz.ht'
		],
		title: 'Allow',
		description: 'Which domains sources are allowed to be loaded from.',
		showInSettings: true,
		split: '\n'
	},
	{
		property: 'colors.background',
		default: '#d6daf0',
		title: 'Background Color',
		showInSettings: true
	},
	{
		property: 'colors.border',
		default: '#b7c5d9',
		title: 'Border Color',
		showInSettings: true
	},
	{
		property: 'colors.odd_row',
		default: '#d6daf0',
		title: 'Odd Row Color',
		showInSettings: true
	},
	{
		property: 'colors.even_row',
		default: '#b7c5d9',
		title: 'Even Row Color',
		showInSettings: true
	},
	{
		property: 'colors.playing',
		default: '#98bff7',
		title: 'Playing Row Color',
		showInSettings: true
	},
	{
		property: 'colors.expander',
		default: '#808bbf',
		title: 'Expander Color',
		showInSettings: true
	},
	{
		property: 'colors.expander_hover',
		default: '#9aa6e1',
		title: 'Expander Hover Color',
		showInSettings: true
	}
]

	const headerOptions = {
	repeat: {
		all: { title: 'Repeat All', text: '[RA]', class: 'fa-repeat' },
		one: { title: 'Repeat One', text: '[R1]', class: 'fa-repeat fa-repeat-one' },
		none: { title: 'No Repeat', text: '[R0]', class: 'fa-repeat disabled' }
	},
	shuffle: {
		true: { title: 'Shuffle', text: '[S]', class: 'fa-random' },
		false: { title: 'Ordered', text: '[S]', class: 'fa-random disabled' },
	},
	playlist: {
		true: { title: 'Hide Playlist', text: '[+]', class: 'fa-expand' },
		false: { title: 'Show Playlist', text: '[+]', class: 'fa-compress' }
	}
}

const Player = {
	ns,

	sounds: [],
	container: null,
	ui: {},
	settings: settingsConfig.reduce((settings, settingConfig) => {
		return _.set(settings, settingConfig.property, settingConfig.default);
	}, {}),

	$: (...args) => Player.container.querySelector(...args),

	// The templates are setup at initialization.
	templates: {},
	_templates: {
		css: "#<%= ns %>-container {\n\tposition: fixed;\n\tbackground: <%= data.colors.background %>;\n\tborder: 1px solid <%= data.colors.border %>;\n\tdisplay: relative;\n\tmin-height: 200px;\n\tmin-width: 100px;\n}\n.<%= ns %>-show-settings .<%= ns %>-player {\n\tdisplay: none;\n}\n.<%= ns %>-setting {\n\tdisplay: none;\n}\n.<%= ns %>-settings {\n\tdisplay: none;\n\tpadding: .25rem;\n}\n.<%= ns %>-show-settings .<%= ns %>-settings {\n\tdisplay: block;\n}\n.<%= ns %>-settings .<%= ns %>-setting-header {\n\tfont-weight: 600;\n\tmargin-top: 0.25rem;\n}\n.<%= ns %>-settings textarea {\n\tborder: solid 1px <%= data.colors.border %>;\n\tmin-width: 100%;\n\tmin-height: 4rem;\n\tbox-sizing: border-box;\n}\n.<%= ns %>-title {\n\tcursor: grab;\n\ttext-align: center;\n\tborder-bottom: solid 1px <%= data.colors.border %>;\n\tpadding: .25rem 0;\n}\nhtml.fourchan-x .<%= ns %>-title a {\n\tfont-size: 0;\n\tvisibility: hidden;\n\tmargin: 0 0.15rem;\n}\nhtml.fourchan-x  .<%= ns %>-title .fa-repeat.fa-repeat-one::after {\n\tcontent: '1';\n\tfont-size: .5rem;\n\tvisibility: visible;\n\tmargin-left: -1px;\n}\n.<%= ns %>-image-link {\n\ttext-align: center;\n\tdisplay: flex;\n\tjustify-items: center;\n\tjustify-content: center;\n\tborder-bottom: solid 1px <%= data.colors.border %>;\n}\n.<%= ns %>-playlist-view .<%= ns %>-image-link {\n\theight: 125px !important;\n}\n.<%= ns %>-expanded-view .<%= ns %>-image-link {\n\theight: auto ;\n\tmin-height: 125px;\n}\n.<%= ns %>-image-link .<%= ns %>-video {\n\tdisplay: none;\n}\n.<%= ns %>-image-link.<%= ns %>-show-video .<%= ns %>-video {\n\tdisplay: block;\n}\n.<%= ns %>-image-link.<%= ns %>-show-video .<%= ns %>-image {\n\tdisplay: none;\n}\n.<%= ns %>-image, .<%= ns %>-video {\n\theight: 100%;\n\twidth: 100%;\n\tobject-fit: contain;\n}\n.<%= ns %>-audio {\n\twidth: 100%;\n}\n.<%= ns %>-list-container {\n\toverflow: auto;\n}\n.<%= ns %>-expanded-view .<%= ns %>-list-container {\n\tdisplay: none;\n}\n.<%= ns %>-list {\n\tdisplay: grid;\n\tlist-style-type: none;\n\tpadding: 0;\n\tmargin: 0;\n}\n.<%= ns %>-list-item {\n\tlist-style-type: none;\n\tpadding: 0.15rem 0.25rem;\n\twhite-space: nowrap;\n\tcursor: pointer;\n}\n.<%= ns %>-list-item.playing {\n\tbackground: <%= data.colors.playing %> !important;\n}\n.<%= ns %>-list-item:nth-child(n) {\n\tbackground: <%= data.colors.odd_row %>;\n}\n.<%= ns %>-list-item:nth-child(2n) {\n\tbackground: <%= data.colors.even_row %>;\n}\n.<%= ns %>-footer {\n\tpadding: .15rem .25rem;\n\tborder-top: solid 1px <%= data.colors.border %>;\n}\n.<%= ns %>-expander {\n\tposition: absolute;\n\tbottom: 0px;\n\tright: 0px;\n\theight: .75rem;\n\twidth: .75rem;\n\tcursor: se-resize;\n\tbackground: linear-gradient(to bottom right, rgba(0,0,0,0), rgba(0,0,0,0) 50%, <%= data.colors.expander %> 55%, <%= data.colors.expander %> 100%)\n}\n.<%= ns %>-expander:hover {\n\tbackground: linear-gradient(to bottom right, rgba(0,0,0,0), rgba(0,0,0,0) 50%, <%= data.colors.expander_hover %> 55%, <%= data.colors.expander_hover %> 100%)\n}",
		body: "<div id=\"<%= ns %>-container\" class=\"<%= ns %>-<%= data.playlist ? 'playlist' : 'extended' %>-view\" style=\"top: 100px; left: 100px; width: 350px; display: none;\">\n\t<div class=\"<%= ns %>-title\" style=\"display: flex; flex-wrap: wrap; justify-content: between;\">\n\t\t<%= Player.templates.header({ data }) %>\n\t</div>\n\t<div class=\"<%= ns %>-player\">\n\t\t<a class=\"<%= ns %>-image-link\" style=\"height: 128px\" target=\"_blank\">\n\t\t\t<img class=\"<%= ns %>-image\"></img>\n\t\t\t<video class=\"<%= ns %>-video\"></video>\n\t\t</a>\n\t\t<div class=\"<%= ns %>-controls\">\n\t\t\t<div class=\"<%= ns %>-play-pause <%=ns %>-<%= !Player.audio || Player.audio.paused ? 'play' : 'pause' %>>\"></div>\n\t\t\t<div class=\"<%= ns %>-seekbar\"></div>\n\t\t\t<div class=\"<%= ns %>-duration\"></div>\n\t\t\t<div class=\"<%= ns %>-volume\"></div>\n\t\t</div>\n\t\t<audio class=\"<%= ns %>-audio\" controls=\"true\"></audio>\n\t\t<div class=\"<%= ns %>-list-container\" style=\"height: 100px\">\n\t\t\t<ul class=\"<%= ns %>-list\">\n\t\t\t\t<%= Player.templates.list({ data }) %>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div class=\"<%= ns %>-footer\">\n\t\t\t<span class=\"<%= ns %>-count\">0</span> sounds\n\t\t\t<div class=\"<%= ns %>-expander\"></div>\n\t\t</div>\n\t</div>\n\t<div class=\"<%= ns %>-settings\">\n\t\t<%= Player.templates.settings({ data }) %>\n\t</div>\n</div>",
		header: "<div style=\"flex: 0 0 auto; width: auto; max-width: 100%; margin-left: 0.25rem;\">\n\t<% Object.keys(headerOptions).forEach(key => { %>\n\t\t<% let option = headerOptions[key][data[key]] || headerOptions[key][0]; %>\n\t\t<a class=\"<%= ns %>-<%= key %>-button fa <%= option.class %>\" title=\"<%= option.title %>\" href=\"javascript;\">\n\t\t\t<%= option.text %>\n\t\t</a>\n\t<% }) %>\n</div><div style=\"flex-basis: 0; flex-grow: 1; max-width: 100%; width: 100%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">\n\t<%= Player.playing ? Player.playing.title : '4chan Sounds' %>\n</div>\n<div style=\"flex: 0 0 auto; width: auto; max-width: 100%; margin-right: 0.25rem;\">\n\t<a class=\"<%= ns %>-config-button fa fa-wrench\" title=\"Settings\" href=\"javascript;\">Settings</a>\n\t<a class=\"<%= ns %>-close-button fa fa-times\" href=\"javascript;\">X</a>\n</div>",
		list: "<% Player.sounds.forEach(sound => { %>\n\t<li class=\"<%= ns %>-list-item <%= sound.playing ? 'playing' : '' %>\" data-id=\"<%= sound.id %>\">\n\t\t<%= sound.title %>\n\t</li>\n<% }) %>",
		settings: "<% settingsConfig.forEach(setting => { %>\n\t<% if (setting.showInSettings) { %>\n\t\t<div class=\"<%= ns %>-setting-header\" <%= setting.description ? `title=\"${setting.description}\"` : '' %>><%= setting.title %></div>\n\t\t<% if (typeof setting.default === 'boolean') { %>\n\t\t\t<input type=\"checkbox\" data-property=\"<%= setting.property %>\" <%= _.get(data, setting.property, false) ? 'checked' : '' %>></input>\n\t\t<% } else if (Array.isArray(setting.default)) { %>\n\t\t\t<textarea data-property=\"<%= setting.property %>\"><%= _.get(data, setting.property, '').join(setting.split) %></textarea>\n\t\t<% } else { %>\n\t\t\t<input type=\"text\" data-property=\"<%= setting.property %>\" value=\"<%= _.get(data, setting.property, '') %>\"></input>\n\t\t<% } %>\n\t<% } %>\n<% }); %>"
	},

	events: {
		click: {
			[`.${ns}-close-button`]: 'hide',
			[`.${ns}-config-button`]: 'toggleSettings',
			[`.${ns}-shuffle-button`]: 'toggleShuffle',
			[`.${ns}-repeat-button`]: 'toggleRepeat',
			[`.${ns}-playlist-button`]: 'togglePlaylist',
			[`.${ns}-list`]: function (e) {
				const id = e.target.getAttribute('data-id');
				const sound = id && Player.sounds.find(function (sound) {
					return sound.id === '' + id;
				});
				sound && Player.play(sound);
			}
		},
		mousedown: {
			[`.${ns}-title`]: 'initMove',
			[`.${ns}-expander`]: 'initResize'
		},
		focusout: {
			[`.${ns}-settings input, .${ns}-settings textarea`]: 'handleSettingChange'
		},
		change: {
			[`.${ns}-settings input[type=checkbox]`]: 'handleSettingChange'
		}
	},

	initialize: async function () {
		try {
			await Player.loadSettings();
			Player.sounds = [ ];
			Player.playOrder = [ ];

			// Set up the template functions.
			for (let name in Player._templates) {
				Player.templates[name] = _.template(Player._templates[name]);
			}

			if (isChanX) {
				Player.initChanX()
			} else {
				document.querySelectorAll('#settingsWindowLink, #settingsWindowLinkBot').forEach(function (link) {
					const bracket = document.createTextNode('] [');
					const showLink = document.createElement('a');
					showLink.innerHTML = 'Sounds';
					showLink.href = 'javascript;';
					link.parentNode.insertBefore(showLink, link);
					link.parentNode.insertBefore(bracket, link);
					showLink.addEventListener('click', Player.toggleDisplay);
				});
			}

			Player.render();
		} catch (err) {
			_logError('There was an error intiaizing the sound player. Please check the console for details.');
			console.error('[4chan sounds player]');
			// Can't recover so throw this error.
			throw err;
		}
	},

	initChanX: function () {
		if (Player._initedChanX) {
			return;
		}
		Player._initedChanX = true;
		const shortcuts = document.getElementById('shortcuts');
		const showIcon = document.createElement('span');
		shortcuts.insertBefore(showIcon, document.getElementById('shortcut-settings'));

		const attrs = { id: 'shortcut-sounds', class: 'shortcut brackets-wrap', 'data-index': 0 };
		for (let attr in attrs) {
			showIcon.setAttribute(attr, attrs[attr]);
		}
		showIcon.innerHTML = '<a href="javascript:;" title="Sounds" class="fa fa-play-circle">Sounds</a>';
		showIcon.querySelector('a').addEventListener('click', Player.toggleDisplay);
	},

	saveSettings: function () {
		try {
			return GM.setValue(ns + '.settings', JSON.stringify(Player.settings));
		} catch (err) {
			_logError('There was an error saving the sound player settings. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	loadSettings: async function () {
		try {
			let settings = await GM.getValue(ns + '.settings');
			if (!settings) {
				return;
			}
			try {
				settings = JSON.parse(settings);
			} catch(e) {
				return;
			}
			function _mix (to, from) {
				for (let key in from) {
					if (from[key] && typeof from[key] === 'object' && !Array.isArray(from[key])) {
						to[key] || (to[key] = {});
						_mix(to[key], from[key]);
					} else {
						to[key] = from[key];
					}
				}
			}
			_mix(Player.settings, settings);
		} catch (err) {
			_logError('There was an error loading the sound player settings. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	_tplOptions: function () {
		return { data: Player.settings };
	},

	render: async function () {
		try {
			if (Player.container) {
				document.body.removeChild(Player.container);
				document.head.removeChild(Player.stylesheet);
			}

			// Insert the stylesheet
			Player.stylesheet = document.createElement('style');
			Player.stylesheet.innerHTML = Player.templates.css(Player._tplOptions());
			document.head.appendChild(Player.stylesheet);

			// Create the main player
			const el = document.createElement('div');
			el.innerHTML = Player.templates.body(Player._tplOptions());
			Player.container = el.querySelector(`#${ns}-container`);
			document.body.appendChild(Player.container);

			// Keep track of the audio element
			Player.audio = Player.$(`.${ns}-audio`);

			// Wire up event listeners.
			for (let evt in Player.events) {
				Player.container.addEventListener(evt, function (e) {
					for (let selector in Player.events[evt]) {
						let handler = Player.events[evt][selector];
						if (typeof handler === 'string') {
							handler = Player[handler];
						}
						const eventTarget = e.target.closest(selector);
						if (eventTarget) {
							e.eventTarget = eventTarget;
							return handler(e);
						}
					}
				});
			}

			// Add audio event listeners. They don't bubble so do them separately.
			Player.audio.addEventListener('ended', Player.next);
			Player.audio.addEventListener('pause', () => Player.$(`.${ns}-video`).pause());
			Player.audio.addEventListener('play', () => {
				Player.$(`.${ns}-video`).currentTime = Player.audio.currentTime;
				Player.$(`.${ns}-video`).play();
			});
			Player.audio.addEventListener('seeked', () => Player.$(`.${ns}-video`).currentTime = Player.audio.currentTime);
		} catch (err) {
			_logError('There was an error rendering the sound player. Please check the console for details.');
			console.error('[4chan sounds player]');
			// Can't recover, throw.
			throw err;
		}
	},

	renderHeader: function () {
		Player.$(`.${ns}-title`).innerHTML = Player.templates.header(Player._tplOptions());
	},

	renderList: function () {
		if (Player.$(`.${ns}-list`)) {
			Player.$(`.${ns}-list`).innerHTML = Player.templates.list(Player._tplOptions());
		}
	},

	toggleDisplay: function (e) {
		e && e.preventDefault();
		if (Player.container.style.display === 'none') {
			Player.show();
		} else {
			Player.hide();
		}
	},

	hide: function (e) {
		try {
			e && e.preventDefault();
			Player.container.style.display = 'none';
			if (Player.settings.pauseOnHide) {
				Player.pause();
			}
		} catch (err) {
			_logError('There was an error hiding the sound player. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	show: async function (e) {
		try {
			e && e.preventDefault();
			if (!Player.container.style.display) {
				return;
			}
			Player.container.style.display = null;
			// Apply the last position/size
			const [ top, left ] = (await GM.getValue(ns + '.position') || '').split(':');
			const [ width, height ] = (await GM.getValue(ns + '.size') || '').split(':');
			+width && +height && Player.resizeTo(width, height);
			+top && +left && Player.moveTo(top, left);
		} catch (err) {
			_logError('There was an error showing the sound player. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},
	
	toggleRepeat: function (e) {
		try {
			e.preventDefault();
			const options = Object.keys(headerOptions.repeat);
			const current = options.indexOf(Player.settings.repeat);
			Player.settings.repeat = options[(current + 4) % 3];
			Player.renderHeader();
			Player.saveSettings();
		} catch (err) {
			_logError('There was an error changing the repeat setting. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},
	
	toggleShuffle: function (e) {
		try {
			e.preventDefault();
			Player.settings.shuffle = !Player.settings.shuffle;
			Player.renderHeader();

			// Update the play order.
			if (!Player.settings.shuffle) {
				Player.playOrder = [ ...Player.sounds ];
			} else {
				const playOrder = Player.playOrder;
				for (let i = playOrder.length - 1; i > 0; i--) {
					const j = Math.floor(Math.random() * (i + 1));
					[playOrder[i], playOrder[j]] = [playOrder[j], playOrder[i]];
				}
			}
			Player.saveSettings();
		} catch (err) {
			_logError('There was an error changing the shuffle setting. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	toggleSettings: function (e) {
		try {
			e.preventDefault();
			if (Player.container.classList.contains(ns + '-show-settings')) {
				Player.container.classList.remove(ns + '-show-settings');
			} else {
				Player.container.classList.add(ns + '-show-settings');
			}
		} catch (err) {
			_logError('There was an error rendering the sound player settings. Please check the console for details.');
			console.error('[4chan sounds player]');
			// Can't recover, throw.
			throw err;
		}
	},

	handleSettingChange: function (e) {
		try {
			const input = e.eventTarget;
			const property = input.getAttribute('data-property');
			const settingConfig = settingsConfig.find(settingConfig => settingConfig.property === property);
			const currentValue = _.get(Player.settings, property);
			let newValue = input[input.getAttribute('type') === 'checkbox' ? 'checked' : 'value'];
			if (settingConfig && settingConfig.split) {
				newValue = newValue.split(decodeURIComponent(settingConfig.split));
			}
			// Not the most stringent check but enough to avoid some spamming.
			if (currentValue !== newValue) {
				// Update the setting.
				_.set(Player.settings, property, newValue);

				// Update the stylesheet reflect any changes.
				Player.stylesheet.innerHTML = Player.templates.css(Player._tplOptions());

				// Save the new settings.
				Player.saveSettings();
			}
		} catch (err) {
			_logError('There was an updating the setting. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	initResize: function initDrag(e) {
		disableUserSelect();
		Player._startX = e.clientX;
		Player._startY = e.clientY;
		Player._startWidth = parseInt(document.defaultView.getComputedStyle(Player.container).width, 10);
		Player._startHeight = parseInt(document.defaultView.getComputedStyle(Player.container).height, 10);
		document.documentElement.addEventListener('mousemove', Player.doResize, false);
		document.documentElement.addEventListener('mouseup', Player.stopResize, false);
	},

	doResize: function(e) {
		Player.resizeTo(Player._startWidth + e.clientX - Player._startX, Player._startHeight + e.clientY - Player._startY);
	},

	resizeTo: function (width, height) {
		// Make sure the player isn't going off screen. 40 to give a bit of spacing for the 4chanX header.
		height = Math.min(height, document.documentElement.clientHeight - 40);

		Player.container.style.width = width + 'px';

		// Change the height of the playlist of image.
		const heightElement = Player.settings.playlist
			? Player.$(`.${ns}-list-container`)
			: Player.$(`.${ns}-image-link`);

		const containerHeight = parseInt(document.defaultView.getComputedStyle(Player.container).height, 10);
		const offset = containerHeight - parseInt(heightElement.style.height, 10);
		heightElement.style.height = Math.max(10, height - offset) + 'px';
	},

	stopResize: function() {
		const style = document.defaultView.getComputedStyle(Player.container);
		document.documentElement.removeEventListener('mousemove', Player.doResize, false);
		document.documentElement.removeEventListener('mouseup', Player.stopResize, false);
		enableUserSelect();
		GM.setValue(ns + '.size', parseInt(style.width, 10) + ':' + parseInt(style.height, 10));
	},

	initMove: function (e) {
		disableUserSelect();
		Player.$(`.${ns}-title`).style.cursor = 'grabbing';

		// Try to reapply the current sizing to fix oversized winows.
		const style = document.defaultView.getComputedStyle(Player.container);
		Player.resizeTo(parseInt(style.width, 10), parseInt(style.height, 10));

		Player._offsetX = e.clientX - Player.container.offsetLeft;
		Player._offsetY = e.clientY - Player.container.offsetTop;
		document.documentElement.addEventListener('mousemove', Player.doMove, false);
		document.documentElement.addEventListener('mouseup', Player.stopMove, false);
	},

	doMove: function (e) {
		Player.moveTo(e.clientX - Player._offsetX, e.clientY - Player._offsetY);
	},

	moveTo: function (x, y) {
		const style = document.defaultView.getComputedStyle(Player.container);
		const maxX = document.documentElement.clientWidth - parseInt(style.width, 10);
		const maxY = document.documentElement.clientHeight - parseInt(style.height, 10);
		Player.container.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
		Player.container.style.top = Math.max(0, Math.min(y, maxY)) + 'px';
	},

	stopMove: function (e) {
		document.documentElement.removeEventListener('mousemove', Player.doMove, false);
		document.documentElement.removeEventListener('mouseup', Player.stopMove, false);
		Player.$(`.${ns}-title`).style.cursor = null;
		enableUserSelect();
		GM.setValue(ns + '.position', parseInt(Player.container.style.left, 10) + ':' + parseInt(Player.container.style.top, 10));
	},

	showThumb: function (sound) {
		try {
			Player.$(`.${ns}-image-link`).classList.remove(ns + '-show-video');
			Player.$(`.${ns}-image`).src = sound.thumb;
			Player.$(`.${ns}-image-link`).href = sound.image;
		} catch (err) {
			_logError('There was an error displaying the sound player image. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	showImage: function (sound) {
		try {
			Player.$(`.${ns}-image-link`).classList.remove(ns + '-show-video');
			Player.$(`.${ns}-image`).src = sound.image;
			Player.$(`.${ns}-image-link`).href = sound.image;
		} catch (err) {
			_logError('There was an error display the sound player image. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	playVideo: function (sound) {
		try {
			Player.$(`.${ns}-image-link`).classList.add(ns + '-show-video');
			Player.$(`.${ns}-video`).src = sound.image;
			Player.$(`.${ns}-video`).play();
		} catch (err) {
			_logError('There was an error display the sound player image. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	hidePlaylist: function () {
		try {
			Player.settings.playlist = false;
			Player.container.classList.add(`${ns}-expanded-view`);
			Player.container.classList.remove(`${ns}-playlist-view`);
			Player.saveSettings();
		} catch (err) {
			_logError('There was an error switching to image view. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	showPlaylist: function () {
		try {
			Player.settings.playlist = true;
			Player.container.classList.remove(`${ns}-expanded-view`);
			Player.container.classList.add(`${ns}-playlist-view`);
			Player.saveSettings();
		} catch (err) {
			_logError('There was an error switching to playlist view. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	togglePlaylist: function (e) {
		e && e.preventDefault();
		if (Player.settings.playlist) {
			Player.hidePlaylist();
		} else {
			Player.showPlaylist();
		}
	},

	add: function (title, id, src, thumb, image) {
		try {
			// Avoid duplicate additions.
			if (Player.sounds.find(sound => sound.id === id)) {
				return;
			}
			const sound = { title, src, id, thumb, image };
			Player.sounds.push(sound);

			// Add the sound to the play order at the end, or someone random for shuffled.
			const index = Player.settings.shuffle
				? Math.floor(Math.random() * Player.sounds.length - 1)
				: Player.sounds.length;
			Player.playOrder.splice(index, 0, sound);

			// Re-render the list.
			Player.renderList();
			Player.$(`.${ns}-count`).innerHTML = Player.sounds.length;

			// If nothing else has been added yet show the image for this sound.
			if (Player.playOrder.length === 1) {
				// If we're on a thread with autoshow enabled then make sure the player is displayed
				if (/\/thread\//.test(location.href) && Player.settings.autoshow) {
					Player.show();
				}
				Player.showThumb(sound);
			}
		} catch (err) {
			_logError('There was an error adding to the sound player. Please check the console for details.');
			console.log('[4chan sounds player', title, id, src, thumg, image);
			console.error('[4chan sounds player]', err);
		}
	},

	play: function (sound) {
		try {
			if (sound) {
				if (Player.playing) {
					Player.playing.playing = false;
				}
				sound.playing = true;
				Player.playing = sound;
				Player.renderHeader();
				Player.audio.src = sound.src;
				if (sound.image.endsWith('.webm')) {
					Player.playVideo(sound);
				} else {
					Player.showImage(sound);
				}
				Player.renderList();
			}
			Player.audio.play();
		} catch (err) {
			_logError('There was an error playing the sound. Please check the console for details.');
			console.error('[4chan sounds player]', err);
		}
	},

	pause: function () {
		Player.audio.pause();
	},

	next: function () {
		Player._movePlaying(1);
	},

	previous: function () {
		Player._movePlaying(-1);
	},

	_movePlaying: function (direction) {
		try {
			// If there's no sound fall out.
			if (!Player.playOrder.length) {
				return;
			}
			// If there's no sound currently playing or it's not in the list then just play the first sound.
			const currentIndex = Player.playOrder.indexOf(Player.playing);
			if (currentIndex === -1) {
				return Player.playSound(Player.playOrder[0]);
			}
			// Get the next index, either repeating the same, wrapping round to repeat all or just moving the index.
			const nextIndex = Player.settings.repeat === 'one'
				? currentIndex
				: Player.settings.repeat === 'all'
					? ((currentIndex + direction) + Player.playOrder.length) % Player.playOrder.length
					: currentIndex + direction;
			const nextSound = Player.playOrder[nextIndex];
			nextSound && Player.play(nextSound);
		} catch (err) {
			_logError(`There was an error selecting the ${direction > 0 ? 'next': 'previous'} track. Please check the console for details.`);
			console.error('[4chan sounds player]', err);
		}
	}
};


	_.templateSettings.imports.ns = ns;
	_.templateSettings.imports.Player = Player;
	_.templateSettings.imports.settingsConfig = settingsConfig;
	_.templateSettings.imports.headerOptions = headerOptions;

	document.addEventListener('DOMContentLoaded', async function() {
		await Player.initialize();

		parseFiles(document.body);

		const observer = new MutationObserver(function (mutations) {
			mutations.forEach(function (mutation) {
				if (mutation.type === 'childList') {
					mutation.addedNodes.forEach(function (node) {
						if (node.nodeType === Node.ELEMENT_NODE) {
							parseFiles(node);
						}
					});
				}
			});
		});

		observer.observe(document.body, {
			childList: true,
			subtree: true
		});
	});

	document.addEventListener('4chanXInitFinished', function () {
		isChanX = true;
		Player.initChanX();
	});

	function parseFiles (target) {
		target.querySelectorAll('.post').forEach(function (post) {
			if (post.parentElement.parentElement.id === 'qp' || post.parentElement.classList.contains('noFile')) {
				return;
			}
			post.querySelectorAll('.file').forEach(function (file) {
				parseFile(file, post);
			});
		});
	};

	function parseFile(file, post) {
		try {
			if (!file.classList.contains('file')) {
				return;
			}

			const fileLink = isChanX
				? file.querySelector('.fileText .file-info > a')
				: file.querySelector('.fileText > a');

			if (!fileLink) {
				return;
			}

			if (!fileLink.href) {
				return;
			}

			let fileName = null;

			if (isChanX) {
				[
					file.querySelector('.fileText .file-info .fnfull'),
					file.querySelector('.fileText .file-info > a')
				].some(function (node) {
					return node && (fileName = node.textContent);
				});
			} else {
				[
					file.querySelector('.fileText'),
					file.querySelector('.fileText > a')
				].some(function (node) {
					return node && (fileName = node.title || node.tagName === 'A' && node.textContent);
				});
			}

			if (!fileName) {
				return;
			}

			fileName = fileName.replace(/\-/, '/');

			const match = fileName.match(/^(.*)[\[\(\{](?:audio|sound)[ \=\:\|\$](.*?)[\]\)\}]/i);

			if (!match) {
				return;
			}

			const id = post.id.slice(1);
			const name = match[1] || id;
			const fileThumb = post.querySelector('.fileThumb');
			const fullSrc = fileThumb && fileThumb.href;
			const thumbSrc = fileThumb && fileThumb.querySelector('img').src;
			let link = match[2];

			if (link.includes('%')) {
				try {
					link = decodeURIComponent(link);
				} catch (error) {
					return;
				}
			}

			if (link.match(/^(https?\:)?\/\//) === null) {
				link = (location.protocol + '//' + link);
			}

			try {
				link = new URL(link);
			} catch (error) {
				return;
			}

			for (let item of Player.settings.allow) {
				if (link.hostname.toLowerCase() === item || link.hostname.toLowerCase().endsWith('.' + item)) {
					return Player.add(name, id, link.href, thumbSrc, fullSrc);
				}
			}
		} catch (err) {
			_logError('There was an issue parsing the files. Please check the console for details.');
			console.log('[4chan sounds player]', post)
			console.error(err);
		}
	};

	function disableUserSelect () {
		document.body.style.userSelect = 'none';
		document.body.style.MozUserSelect = 'none';
	}

	function enableUserSelect () {
		document.body.style.userSelect = null;
		document.body.style.MozUserSelect = null;
	}
})();