GM_config

GreaseMonkey Script Configurator

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/1884/4836/GM_config.js

  1. // ==UserScript==
  2. // @name GM_config
  3. // @namespace http://userscripts.org/users/23652
  4. // @description GreaseMonkey Script Configurator
  5. // @include http://*
  6. // @include https://*
  7. // @include file:*
  8. // @copyright JoeSimmons & Sizzlemctwizzle & IzzySoft
  9. // @version 1.2.57
  10. // @license LGPL version 3 or any later version; http://www.gnu.org/copyleft/lgpl.html
  11. // ==/UserScript==
  12.  
  13. /* Instructions ---------------------------------------------------------------------------------------------------
  14.  
  15. GM_config is cross-browser compatible.
  16.  
  17. To use it in a Greasemonkey/Tampermonkey/Violentmonkey script, you can just @require it.
  18.  
  19. If you can't @require it, you will need to manually include the code at the beginning of your user script.
  20.  
  21. In non-Greasemonkey, stored settings will only be accessible on the same domain in which they were saved.
  22.  
  23. ---------------------------------------------------------------------------------------------------------------- */
  24.  
  25.  
  26.  
  27. /* CHANGELOG --------------------------------------------------------------------------
  28.  
  29. 1.2.57
  30. - Fixed TypeError with the "options" setting of a "select" field
  31. The "options" setting needs to be a JSON object, not an array
  32.  
  33. 1.2.56
  34. - started keeping changelog in source
  35. - added type "password"
  36. - fixed error when user doesn't define any sections
  37.  
  38. ------------------------------------------------------------------------------------- */
  39.  
  40.  
  41.  
  42. var GM_config = {
  43. storage: 'GM_config',
  44. init: function() {
  45. // loop through GM_config.init() arguments
  46. for(var i=0,l=arguments.length,arg; i<l; ++i) {
  47. arg=arguments[i];
  48. switch(typeof arg) {
  49. case 'object': for(var j in arg) { // could be a callback functions or settings object
  50. switch(j) {
  51. case "open": GM_config.onOpen=arg[j]; delete arg[j]; break; // called when frame is gone
  52. case "close": GM_config.onClose=arg[j]; delete arg[j]; break; // called when settings have been saved
  53. case "save": GM_config.onSave=arg[j]; delete arg[j]; break; // store the settings objects
  54. default: var settings = arg;
  55. }
  56. } break;
  57. case 'function': GM_config.onOpen = arg; break; // passing a bare function is set to open
  58. // could be custom CSS or the title string
  59. case 'string': if(arg.indexOf('{') !== -1 && arg.indexOf('}') !== -1) var css = arg;
  60. else GM_config.title = arg;
  61. break;
  62. }
  63. }
  64. if(!GM_config.title) GM_config.title = 'Settings - Anonymous Script'; // if title wasn't passed through init()
  65.  
  66. // give the script a unique saving ID for non-firefox browsers
  67. GM_config.storage = GM_config.title.replace(/\W+/g, "").toLowerCase();
  68.  
  69. var stored = GM_config.read(); // read the stored settings
  70. GM_config.passed_values = {};
  71. for(var i in settings) {
  72. GM_config.doSettingValue(settings, stored, i, null, false);
  73. if(settings[i].kids) for(var kid in settings[i].kids) GM_config.doSettingValue(settings, stored, kid, i, true);
  74. }
  75. GM_config.values = GM_config.passed_values;
  76. GM_config.settings = settings;
  77. if (css) GM_config.css.stylish = css;
  78. },
  79. open: function() {
  80. if(document.evaluate("//iframe[@id='GM_config']",document,null,9,null).singleNodeValue) return;
  81. // Create frame
  82. document.body.appendChild((GM_config.frame=GM_config.create('iframe',{id:'GM_config', style:'position: fixed; top: 0; left: 0; opacity: 0; display: none; z-index: 999999; width: 75%; height: 75%; max-height: 95%; max-width: 95%; border:3px ridge #000000; overflow: auto;'})));
  83. GM_config.frame.src = 'about:blank'; // In WebKit src cant be set until it is added to the page
  84. GM_config.frame.addEventListener('load', function() {
  85. var obj = GM_config, doc = this.contentDocument, frameBody = doc.getElementsByTagName('body')[0], create=obj.create, settings=obj.settings, anch, secNo;
  86. obj.frame.contentDocument.getElementsByTagName('head')[0].appendChild(create('style',{type:'text/css',textContent:obj.css.basic + "\n\n" + obj.css.stylish}));
  87.  
  88. // Add header and title
  89. frameBody.appendChild(create('div', {id:'header',className:'config_header block center', innerHTML:obj.title}));
  90.  
  91. // Append elements
  92. anch = frameBody; // define frame body
  93. secNo = 0; // anchor to append elements
  94. for(var i in settings) {
  95. var type, field = settings[i], value = obj.values[i], section = (field.section ? field.section : ["Main Options"]),
  96. headerExists = doc.evaluate(".//div[@class='section_header_holder' and starts-with(@id, 'section_')]", frameBody, null, 9, null).singleNodeValue;
  97.  
  98. if(typeof field.section !== "undefined" || headerExists === null) {
  99. anch = frameBody.appendChild(create('div', {className:'section_header_holder', id:'section_'+secNo, kids:new Array(
  100. create('a', {className:'section_header center', href:"javascript:void(0);", id:'c_section_kids_'+secNo, textContent:section[0], onclick:function(){GM_config.toggle(this.id.substring(2));}}),
  101. create('div', {id:'section_kids_'+secNo, className:'section_kids', style:obj.getValue('section_kids_'+secNo, "")==""?"":"display: none;"})
  102. )}));
  103. if(section[1]) anch.appendChild(create('p', {className:'section_desc center',innerHTML:section[1]}));
  104. secNo++;
  105. }
  106. anch.childNodes[1].appendChild(GM_config.addToFrame(field, i, false));
  107. }
  108.  
  109. // Add save and close buttons
  110. frameBody.appendChild(obj.create('div', {id:'buttons_holder', kids:new Array(
  111. obj.create('button',{id:'saveBtn',textContent:'Save',title:'Save options and close window',className:'saveclose_buttons',onclick:function(){GM_config.close(true)}}),
  112. obj.create('button',{id:'cancelBtn', textContent:'Cancel',title:'Close window',className:'saveclose_buttons',onclick:function(){GM_config.close(false)}}),
  113. obj.create('div', {className:'reset_holder block', kids:new Array(
  114. obj.create('a',{id:'resetLink',textContent:'Restore to default',href:'#',title:'Restore settings to default configuration',className:'reset',onclick:obj.reset})
  115. )}))}));
  116.  
  117. obj.center(); // Show and center it
  118. window.addEventListener('resize', obj.center, false); // Center it on resize
  119. if (obj.onOpen) obj.onOpen(); // Call the open() callback function
  120. // Close frame on window close
  121. window.addEventListener('beforeunload', function(){GM_config.remove(this);}, false);
  122. }, false);
  123. },
  124. close: function(save) {
  125. if(save) {
  126. var type, fields = GM_config.settings, typewhite=/radio|text|hidden|password|checkbox/;
  127. for(f in fields) {
  128. var field = GM_config.frame.contentDocument.getElementById('field_'+f), kids=fields[f].kids;
  129. if(typewhite.test(field.type)) type=field.type;
  130. else type=field.tagName.toLowerCase();
  131. GM_config.doSave(f, field, type);
  132. if(kids) for(var kid in kids) {
  133. var field = GM_config.frame.contentDocument.getElementById('field_'+kid);
  134. if(typewhite.test(field.type)) type=field.type;
  135. else type=field.tagName.toLowerCase();
  136. GM_config.doSave(kid, field, type, f);
  137. }
  138. }
  139. if(GM_config.onSave) GM_config.onSave(); // Call the save() callback function
  140. GM_config.save();
  141. }
  142. if(GM_config.frame) GM_config.remove(GM_config.frame);
  143. delete GM_config.frame;
  144. if(GM_config.onClose) GM_config.onClose(); // Call the close() callback function
  145. },
  146. set: function(name,val) {
  147. GM_config.values[name] = val;
  148. },
  149. get: function(name) {
  150. return GM_config.values[name];
  151. },
  152. isGM: (typeof window.opera === "undefined" && typeof window.chrome === "undefined" && typeof GM_info === "object" && typeof GM_registerMenuCommand === "function"),
  153. log: function(str) {
  154.  
  155. if(this.isGM) return GM_log(str);
  156. else if(window.opera) return window.opera.postError(str);
  157. else return console.log(str);
  158.  
  159. },
  160. getValue : function(name, d) {
  161. var r, def = (typeof d !== "undefined" ? d : "");
  162. switch(this.isGM === true) {
  163. case true: r = GM_getValue(name, def); break;
  164. case false: r = localStorage.getItem(name) || def; break;
  165. }
  166. return r;
  167. },
  168. setValue : function(name, value) {
  169. switch(this.isGM === true) {
  170. case true: GM_setValue(name, value); break;
  171. case false: localStorage.setItem(name, value); break;
  172. }
  173. },
  174. deleteValue : function(name) {
  175. switch(this.isGM === true) {
  176. case true: GM_deleteValue(name); break;
  177. case false: localStorage.removeItem(name); break;
  178. }
  179. },
  180. save: function(store, obj) {
  181. try {
  182. var val = JSON.stringify(obj || GM_config.values);
  183. GM_config.setValue((store||GM_config.storage),val);
  184. } catch(e) {
  185. GM_config.log("GM_config failed to save settings!\n" + e);
  186. }
  187. },
  188. read: function(store) {
  189. var val = GM_config.getValue((store || GM_config.storage), '{}');
  190. switch(typeof val) {
  191. case "string": var rval = JSON.parse(val); break;
  192. case "object": var rval = val; break;
  193. default: var rval = {};
  194. }
  195. return rval;
  196. },
  197. reset: function(e) {
  198. e.preventDefault();
  199. var type, obj = GM_config, fields = obj.settings;
  200. for(f in fields) {
  201. var field = obj.frame.contentDocument.getElementById('field_'+f), kids=fields[f].kids;
  202. if(field.type=='radio'||field.type=='text'||field.type=='checkbox') type=field.type;
  203. else type=field.tagName.toLowerCase();
  204. GM_config.doReset(field, type, null, f, null, false);
  205. if(kids) for(var kid in kids) {
  206. var field = GM_config.frame.contentDocument.getElementById('field_'+kid);
  207. if(field.type=='radio'||field.type=='text'||field.type=='checkbox') type=field.type;
  208. else type=field.tagName.toLowerCase();
  209. GM_config.doReset(field, type, f, kid, true);
  210. }
  211. }
  212. },
  213. addToFrame : function(field, i, k) {
  214. var elem, obj = this, anch = this.frame, value = obj.values[i], Options = field.options, label = field.label, create=obj.create, isKid = (k !== null && k === true);
  215. switch(field.type) {
  216. case 'textarea':
  217. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  218. create('span', {textContent:label, className:'field_label'}),
  219. create('textarea', {id:'field_'+i,innerHTML:value, cols:(field.cols?field.cols:20), rows:(field.rows?field.rows:2)})
  220. ), className: 'config_var'});
  221. break;
  222. case 'radio':
  223. var boxes = new Array();
  224. for (var j = 0,len = Options.length; j<len; j++) {
  225. boxes.push(create('span', {textContent:Options[j]}));
  226. boxes.push(create('input', {value:Options[j], type:'radio', name:i, checked:Options[j]==value?true:false}));
  227. }
  228. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  229. create('span', {textContent:label, className:'field_label'}),
  230. create('span', {id:'field_'+i, kids:boxes})
  231. ), className: 'config_var'});
  232. break;
  233. case 'select':
  234. var options = [];
  235. if(typeof Options === "object" && typeof Options.push !== "function") for(var j in Options) options.push(create('option',{textContent:Options[j],value:j,selected:(j==value)}));
  236. else options.push(create("option", {textContent:"Error - \"options\" needs to be a JSON object.", value:"error", selected:"selected"}));
  237. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  238. create('span', {textContent:label, className:'field_label'}),
  239. create('select',{id:'field_'+i, kids:options})
  240. ), className: 'config_var'});
  241. break;
  242. case 'checkbox':
  243. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  244. create('label', {textContent:label, className:'field_label', "for":'field_'+i}),
  245. create('input', {id:'field_'+i, type:'checkbox', value:value, checked:value})
  246. ), className: 'config_var'});
  247. break;
  248. case 'button':
  249. var tmp;
  250. elem = create(isKid ? "span" : "div", {kids:new Array(
  251. (tmp=create('input', {id:'field_'+i, type:'button', value:label, size:(field.size?field.size:25), title:field.title||''}))
  252. ), className: 'config_var'});
  253. if(field.script) obj.addEvent(tmp, 'click', field.script);
  254. break;
  255. case 'hidden':
  256. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  257. create('input', {id:'field_'+i, type:'hidden', value:value})
  258. ), className: 'config_var'});
  259. break;
  260. case 'password':
  261. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  262. create('span', {textContent:label, className:'field_label'}),
  263. create('input', {id:'field_'+i, type:'password', value:value, size:(field.size?field.size:25)})
  264. ), className: 'config_var'});
  265. break;
  266. default:
  267. elem = create(isKid ? "span" : "div", {title:field.title||'', kids:new Array(
  268. create('span', {textContent:label, className:'field_label'}),
  269. create('input', {id:'field_'+i, type:'text', value:value, size:(field.size?field.size:25)})
  270. ), className: 'config_var'});
  271. }
  272. if(field.kids) {
  273. var kids=field.kids;
  274. for(var kid in kids) elem.appendChild(obj.addToFrame(kids[kid], kid, true));
  275. }
  276. return elem;
  277. },
  278. doSave : function(f, field, type, oldf) {
  279. var isNum=/^[\d\.]+$/, set = oldf ? GM_config.settings[oldf]["kids"] : GM_config.settings;
  280. switch(type) {
  281. case 'text':
  282. GM_config.values[f] = ((set[f].type=='text') ? field.value : ((isNum.test(field.value) && ",int,float".indexOf(","+set[f].type)!=-1) ? parseFloat(field.value) : false));
  283. if(set[f]===false) {
  284. alert('Invalid type for field: '+f+'\nPlease use type: '+set[f].type);
  285. return;
  286. }
  287. break;
  288. case 'hidden': case 'password':
  289. GM_config.values[f] = field.value.toString();
  290. break;
  291. case 'textarea':
  292. GM_config.values[f] = field.value;
  293. break;
  294. case 'checkbox':
  295. GM_config.values[f] = field.checked;
  296. break;
  297. case 'select':
  298. GM_config.values[f] = field.options[field.selectedIndex].value;
  299. break;
  300. case 'span':
  301. var radios = field.getElementsByTagName('input');
  302. if(radios.length>0) for(var i=radios.length-1; i>=0; i--) {
  303. if(radios[i].checked) GM_config.values[f] = radios[i].value;
  304. }
  305. break;
  306. }
  307. },
  308. doSettingValue : function(settings, stored, i, oldi, k) {
  309. var set = k!=null && k==true && oldi!=null ? settings[oldi]["kids"][i] : settings[i];
  310. if(",save,open,close".indexOf(","+i) == -1) {
  311. // The code below translates to:
  312. // if a setting was passed to init but wasn't stored then
  313. // if a default value wasn't passed through init() then use null
  314. // else use the default value passed through init()
  315. // else use the stored value
  316. try {
  317. var value = (stored[i]==undefined ? (set["default"]==undefined ? null : set["default"]) : stored[i]);
  318. } catch(e) {
  319. var value = (stored[i]=="undefined" ? (set["default"]=="undefined" ? null : set["default"]) : stored[i]);
  320. }
  321. // If the value isn't stored and no default was passed through init()
  322. // try to predict a default value based on the type
  323. if (value === null) {
  324. switch(set["type"]) {
  325. case 'radio': case 'select':
  326. value = set.options[0]; break;
  327. case 'checkbox':
  328. value = false; break;
  329. case 'int': case 'float':
  330. value = 0; break;
  331. default:
  332. value = (typeof stored[i]=="function") ? stored[i] : "";
  333. }
  334. }
  335. }
  336. GM_config.passed_values[i] = value;
  337. },
  338. doReset : function(field, type, oldf, f, k) {
  339. var isKid = k!=null && k==true, obj=GM_config,
  340. set = isKid ? obj.settings[oldf]["kids"][f] : obj.settings[f];
  341. switch(type) {
  342. case 'text':
  343. field.value = set['default'] || '';
  344. break;
  345. case 'hidden': case 'password':
  346. field.value = set['default'] || '';
  347. break;
  348. case 'textarea':
  349. field.value = set['default'] || '';
  350. break;
  351. case 'checkbox':
  352. field.checked = set['default'] || false;
  353. break;
  354. case 'select':
  355. if(set['default']) {
  356. for(var i=field.options.length-1; i>=0; i--)
  357. if(field.options[i].value==set['default']) field.selectedIndex=i;
  358. }
  359. else field.selectedIndex=0;
  360. break;
  361. case 'span':
  362. var radios = field.getElementsByTagName('input');
  363. if(radios.length>0) for(var i=radios.length-1; i>=0; i--) {
  364. if(radios[i].value==set['default']) {
  365. radios[i].checked=true;
  366. }
  367. }
  368. break;
  369. }
  370. },
  371. values: {},
  372. settings: {},
  373. css: {
  374. basic: 'body {background:#FFFFFF;}\n' +
  375. '.indent40 {margin-left:40%;}\n' +
  376. '* {font-family: arial, tahoma, sans-serif, myriad pro;}\n' +
  377. '.field_label {font-weight:bold; font-size:12px; margin-right:6px;}\n' +
  378. '.block {display:block;}\n' +
  379. '.saveclose_buttons {\n' +
  380. 'margin:16px 10px 10px 10px;\n' +
  381. 'padding:2px 12px 2px 12px;\n' +
  382. '}\n' +
  383. '.reset, #buttons_holder, .reset a {text-align:right; color:#000000;}\n' +
  384. '.config_header {font-size:20pt; margin:0;}\n' +
  385. '.config_desc, .section_desc, .reset {font-size:9pt;}\n' +
  386. '.center {text-align:center;}\n' +
  387. '.section_header_holder {margin-top:8px;}\n' +
  388. '.config_var {margin:0 0 4px 0; display:block;}\n' +
  389. '.config_var {font-size: 13px !important;}\n' +
  390. '.section_header {font-size:13pt; background:#414141; color:#FFFFFF; border:1px solid #000000; margin:0;}\n' +
  391. '.section_desc {font-size:9pt; background:#EFEFEF; color:#575757; border:1px solid #CCCCCC; margin:0 0 6px 0;}\n' +
  392. 'input[type="radio"] {margin-right:8px;}',
  393. stylish: ''
  394. },
  395. create: function(a,b) {
  396. var ret=window.document.createElement(a);
  397. if(b) for(var prop in b) {
  398. if(prop.indexOf('on')==0) ret.addEventListener(prop.substring(2),b[prop],false);
  399. else if(prop=="kids" && (prop=b[prop])) for(var i=0; i<prop.length; i++) ret.appendChild(prop[i]);
  400. else if(",style,accesskey,id,name,src,href,for".indexOf(","+prop.toLowerCase())!=-1) ret.setAttribute(prop, b[prop]);
  401. else ret[prop]=b[prop];
  402. }
  403. return ret;
  404. },
  405. center: function() {
  406. var node = GM_config.frame, style = node.style, beforeOpacity = style.opacity;
  407. if(style.display=='none') style.opacity='0';
  408. style.display = '';
  409. style.top = Math.floor((window.innerHeight/2)-(node.offsetHeight/2)) + 'px';
  410. style.left = Math.floor((window.innerWidth/2)-(node.offsetWidth/2)) + 'px';
  411. style.opacity = '1';
  412. },
  413. run: function() {
  414. var script=GM_config.getAttribute('script');
  415. if(script && typeof script=='string' && script!='') {
  416. func = new Function(script);
  417. window.setTimeout(func, 0);
  418. }
  419. },
  420. addEvent: function(el, ev, scr) {
  421. if(el) el.addEventListener(ev, function() {
  422. typeof scr === 'function' ? window.setTimeout(scr, 0) : eval(scr)
  423. }, false);
  424. },
  425. remove: function(el) {
  426. if(el && el.parentNode) el.parentNode.removeChild(el);
  427. },
  428. toggle : function(e) {
  429. var node=GM_config.frame.contentDocument.getElementById(e);
  430. node.style.display=(node.style.display!='none')?'none':'';
  431. GM_config.setValue(e, node.style.display);
  432. },
  433. };
  434.  
  435.  
  436. /* EXAMPLE CODE BELOW --------------------------
  437.  
  438. GM_config.init("Test", {
  439. "one" : {
  440. "label" : "Option One",
  441. "type" : "checkbox",
  442. "default" : false
  443. },
  444. "two" : {
  445. "label" : "Option Two",
  446. "type" : "checkbox",
  447. "default" : false
  448. },
  449. "three" : {
  450. "label" : "Option Three",
  451. "type" : "password"
  452. },
  453. "four" : {
  454. "label" : "Option Four",
  455. "type" : "select",
  456. "options" : {
  457. "one" : "One",
  458. "two" : "Two"
  459. }
  460. }
  461. });
  462.  
  463. GM_config.open();
  464.  
  465. ------------------------------------------------- */