Script Updater (userscripts.org)

Dead simple user script update checker for use with your user scripts. Includes simple means of showing your version history through meta tags.

이 스크립트는 직접 설치해서 쓰는 게 아닙니다. 다른 스크립트가 메타 명령 // @require https://update.greasyfork.org/scripts/7847/34757/Script%20Updater%20%28userscriptsorg%29.js(으)로 포함하여 쓰는 라이브러리입니다.

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name Script Updater (userscripts.org)
  3. // @namespace PhasmaExMachina
  4. // @description Dead simple user script update checker for use with your user scripts. Includes simple means of showing your version history through meta tags.
  5. // @version 1.07
  6. //
  7. // @history 1.07 Added SecretaryUpdater.installUrl property to overwrite default install location
  8. // @history 1.06 Fixed parsing of history when there's only one line
  9. // @history 1.06 Fixed conflicts when multiple scripts display update window at the same time
  10. // @history 1.04 Fixed mis-handling of DOM elements on XML pages
  11. // @history 1.03 Screen mask now fills entire document height as intended
  12. // @history 1.03 Notice is now centered and fixed regardless of scrolling
  13. // @history 1.03 Fixed minor wording error
  14. // @history 1.03 Fixed implementation of ScriptUpdater.forceNotce()
  15. // @history 1.03 Fixed implementation of callback functions
  16. // @history 1.02 Improved code performance based on community feedback
  17. // @history 1.01 Removed requirement for current version
  18. // @history 1.01 Cleaned up code
  19. // @history 1.01 Simplified metadata retrieval
  20. // @history 1.01 Updated license
  21. // @history 1.00 Initial release
  22. //
  23. // ==/UserScript==
  24.  
  25. ScriptUpdater = {
  26. version:"1.07",
  27. //------------------------------------------- "public" methods --------------------------------------
  28. check:function(scriptId, currentVersion, callback) {
  29. ScriptUpdater.initVars(scriptId, currentVersion, callback, true, false);
  30. var d = new Date();
  31. if(ScriptUpdater.getInterval() > 0 && d.getTime() - ScriptUpdater.getLastCheck() > ScriptUpdater.getInterval())
  32. ScriptUpdater.checkRemoteScript();
  33. },
  34. forceCheck:function(scriptId, currentVersion, callback) {
  35. ScriptUpdater.initVars(scriptId, currentVersion, callback, true, false);
  36. ScriptUpdater.checkRemoteScript();
  37. },
  38. getLatestVersion:function(scriptId, callback) {
  39. if(typeof(callback) != 'function')
  40. alert("ScriptUpdater error:\n\n scriptUpdater.getLatestVersion() requires a callback function as the third argument");
  41. ScriptUpdater.initVars(scriptId, callback, false, false, false);
  42. ScriptUpdater.checkRemoteScript();
  43. },
  44. forceNotice:function(scriptId, currentVersion, callback) {
  45. ScriptUpdater.initVars(scriptId, currentVersion, callback, true, true);
  46. ScriptUpdater.checkRemoteScript();
  47. },
  48. checkStored:function() {
  49. if(typeof(ScriptUpdater.scriptId) != 'undefined' && typeof(ScriptUpdater.scriptCurrentVersion) != 'undefined') {
  50. return (typeof(GM_getValue('ScriptUpdater_versionAvailable')) != 'undefined' && ScriptUpdater.scriptCurrentVersion.toString() != GM_getValue('ScriptUpdater_versionAvailable').toString());
  51. } else return false;
  52. },
  53. //------------------------------------------- "private" methods --------------------------------------
  54. $:function(id) {
  55. return document.getElementById(id);
  56. },
  57. initVars:function(scriptId, currentVersion, callbackFunction, useNotice, forceNoticeEnabled) {
  58. ScriptUpdater.scriptId = scriptId;
  59. ScriptUpdater.scriptCurrentVersion = typeof(currentVersion) != 'undefined' ? currentVersion.toString() : false;
  60. ScriptUpdater.callbackFunction = typeof(callbackFunction) == 'function' ? callbackFunction : false;
  61. ScriptUpdater.useNotice = useNotice;
  62. ScriptUpdater.forceNoticeEnabled = forceNoticeEnabled;
  63. },
  64. checkRemoteScript:function() {
  65. if(ScriptUpdater.scriptCurrentVersion && !ScriptUpdater.alreadyOffered(ScriptUpdater.scriptCurrentVersion))
  66. ScriptUpdater.addOffer(ScriptUpdater.scriptCurrentVersion);
  67. var d = new Date();
  68. ScriptUpdater.setVal('lastCheck_' + ScriptUpdater.scriptId, d.getTime());
  69. // check the userscripts.org code review page
  70. GM_xmlhttpRequest ({
  71. method: "GET",
  72. url: "http://userscripts.org/scripts/source/" + ScriptUpdater.scriptId + '.meta.js',
  73. headers: {"User-agent": "Mozilla/5.0", "Accept": "text/html"},
  74. onload: function (response) {
  75. ScriptUpdater.meta = ScriptUpdater.parseHeaders(response.responseText);
  76. // store latest version available
  77. GM_setValue('ScriptUpdater_versionAvailable', ScriptUpdater.meta.version);
  78. if(ScriptUpdater.forceNoticeEnabled || (!ScriptUpdater.alreadyOffered(ScriptUpdater.meta.version) && ScriptUpdater.useNotice)) {
  79. if(!ScriptUpdater.alreadyOffered(ScriptUpdater.meta.version))
  80. ScriptUpdater.addOffer(ScriptUpdater.meta.version);
  81. ScriptUpdater.showNotice();
  82. }
  83. if(typeof(ScriptUpdater.callbackFunction) == 'function')
  84. ScriptUpdater.callbackFunction(ScriptUpdater.meta.version);
  85. }
  86. });
  87. },
  88. parseHeaders:function(metadataBlock) {
  89. var source = metadataBlock;
  90. var headers = {};
  91. var tmp = source.match(/\/\/ ==UserScript==((.|\n|\r)*?)\/\/ ==\/UserScript==/);
  92. if (tmp) {
  93. var lines = tmp[0].match(/@(.*?)(\n|\r)/g);
  94. for (var i = 0; i < lines.length; i++) {
  95. var tmp = lines[i].match(/^@([^\s]*?)\s+(.*)/);
  96. var key = tmp[1];
  97. var value = tmp[2];
  98. if (headers[key] && !(headers[key] instanceof Array))
  99. headers[key] = new Array(headers[key]);
  100. if (headers[key] instanceof Array)
  101. headers[key].push(value);
  102. else
  103. headers[key] = value;
  104. }
  105. }
  106. return headers;
  107. },
  108. showNotice:function() {
  109. if(ScriptUpdater.meta.name && ScriptUpdater.meta.version) {
  110. GM_addStyle(
  111. "#ScriptUpdater" + ScriptUpdater.scriptId + "Mask { position:absolute; width:100%; top:0; left:0; height:100%; background-color:#000; opacity:.7; z-index:9000; } \
  112. #ScriptUpdater" + ScriptUpdater.scriptId + "Body * { border:none; font-size:12px; color:#333; font-weight:normal; margin:0; padding:0; background:none; text-decoration:none; font-family:Helvetica Neue,Arial,Helvetica,sans-serif; } \
  113. #ScriptUpdater" + ScriptUpdater.scriptId + "Body { width:500px; margin:auto; top:125px; position:fixed; left:35%; text-align:left; background:#f9f9f9; border:1px outset #333; padding:0; font-family:Arial; font-size:14px; -moz-border-radius:5px; cursor:default; z-index:9010; color:#333; padding-bottom:1em ; } \
  114. #ScriptUpdater" + ScriptUpdater.scriptId + "Body a { margin:0 .5em; text-decoration:underline; color:#000099; font-weight:bold; } \
  115. #ScriptUpdater" + ScriptUpdater.scriptId + "Body strong { font-weight:bold; } \
  116. #ScriptUpdater" + ScriptUpdater.scriptId + "Body h1 { font-size:13px; font-weight:bold; padding:.5em; border-bottom:1px solid #333; background-color:#999; margin-bottom:.75em; } \
  117. #ScriptUpdater" + ScriptUpdater.scriptId + "Body h2 { font-weight:bold; margin:.5em 1em; } \
  118. #ScriptUpdater" + ScriptUpdater.scriptId + "Body h1 a { font-size:13px; font-weight:bold; color:#fff; text-decoration:none; cursor:help; } \
  119. #ScriptUpdater" + ScriptUpdater.scriptId + "Body h1 a:hover { text-decoration:underline; } \
  120. #ScriptUpdater" + ScriptUpdater.scriptId + "Body table { width:auto; margin:0 1em; } \
  121. #ScriptUpdater" + ScriptUpdater.scriptId + "Body table tr th { padding-left:2em; text-align:right; padding-right:.5em; line-height:2em; } \
  122. #ScriptUpdater" + ScriptUpdater.scriptId + "Body table tr td { line-height:2em; font-weight:bold; } \
  123. #ScriptUpdater" + ScriptUpdater.scriptId + "Body li { list-style-type:circle; } \
  124. #ScriptUpdater" + ScriptUpdater.scriptId + "Body p { font-size:12px; font-weight:normal; margin:1em; } \
  125. #ScriptUpdater" + ScriptUpdater.scriptId + "History { margin:0 1em 1em 1em; max-height:150px; overflow-y:auto; border:1px inset #999; padding:0 1em 1em; width:448px; } \
  126. #ScriptUpdater" + ScriptUpdater.scriptId + "History ul { margin-left:2em; } \
  127. #ScriptUpdater" + ScriptUpdater.scriptId + "Close { float:right; cursor:pointer; height:14px; opacity:.5; } \
  128. #ScriptUpdater" + ScriptUpdater.scriptId + "Close:hover { opacity:.9; } \
  129. #ScriptUpdater" + ScriptUpdater.scriptId + "Footer { margin:.75em 1em; } \
  130. #ScriptUpdater" + ScriptUpdater.scriptId + "Footer input { border:1px outset #666; padding:3px 5px 5px 20px; background:no-repeat 4px center #eee; -moz-border-radius:3px; cursor:pointer; width:70px; float:right; margin-left:.5em; } \
  131. #ScriptUpdater" + ScriptUpdater.scriptId + "Footer input:hover { background-color:#f9f9f9; } \
  132. #ScriptUpdater" + ScriptUpdater.scriptId + "Footer select { border:1px inset #666; }"
  133. );
  134. var noticeBg = document.createElement('div');
  135. noticeBg.id = "ScriptUpdater" + ScriptUpdater.scriptId + "Mask";
  136. document.body.appendChild(noticeBg);
  137. var noticeWrapper = document.createElement('div');
  138. noticeWrapper.setAttribute('style', 'position:absolute; width:100%; top:0; left:0; z-index:9010; max-width:auto; min-width:auto; max-height:auto; min-height:auto;');
  139. noticeWrapper.id = "ScriptUpdater" + ScriptUpdater.scriptId + "BodyWrapper";
  140. var html = new Array();
  141. var notice = document.createElement('div');
  142. notice.id = "ScriptUpdater" + ScriptUpdater.scriptId + "Body";
  143. html.push('<h1><img id="ScriptUpdater' + ScriptUpdater.scriptId + 'Close" src="');
  144. html.push(ScriptUpdater.icons.close);
  145. html.push('" title="Close"/><img src="');
  146. html.push(ScriptUpdater.icons.uso);
  147. html.push('" align="absmiddle" style="margin-top:-2px;"/><a href="http://userscripts.org/scripts/show/57756" target="_blank" title="About the Userscripts.org Script Updater v');
  148. html.push(ScriptUpdater.meta.version);
  149. html.push('">Userscripts.org Updater</a></h1>');
  150. if(!ScriptUpdater.forceNoticeEnabled) {
  151. html.push('<p>There is a new version of <strong><a href="http://userscripts.org/scripts/show/');
  152. html.push(ScriptUpdater.scriptId);
  153. html.push('" target="_blank" title="Go to script page">');
  154. html.push(ScriptUpdater.meta.name);
  155. html.push('</a> </strong> available for installation.</p>');
  156. } else {
  157. html.push('<p><strong><a href="http://userscripts.org/scripts/show/');
  158. html.push(ScriptUpdater.scriptId);
  159. html.push('" target="_blank" title="Go to script page" style="margin:0; padding:0;">');
  160. html.push(ScriptUpdater.meta.name);
  161. html.push('</a> </strong></p>');
  162. }
  163. if(ScriptUpdater.scriptCurrentVersion) {
  164. html.push('<p>You currently have version <strong>');
  165. html.push(ScriptUpdater.scriptCurrentVersion)
  166. html.push('</strong> installed. The latest version is <strong>');
  167. html.push(ScriptUpdater.meta.version);
  168. html.push('</strong></p>');
  169. }
  170. if(ScriptUpdater.meta.history) {
  171. html.push('<h2>Version History:</h2><div id="ScriptUpdater' + ScriptUpdater.scriptId + 'History">');
  172. var history = new Array();
  173. var version, desc;
  174. if(typeof(ScriptUpdater.meta.history) != 'string') {
  175. for(var i = 0; i < ScriptUpdater.meta.history.length; i++) {
  176. var tmp = ScriptUpdater.meta.history[i].match(/(\S+)\s+(.*)$/);
  177. version = tmp[1];
  178. change = tmp[2];
  179. history[version] = typeof(history[version]) == 'undefined' ? new Array() : history[version];
  180. history[version].push(change);
  181. }
  182. } else {
  183. var tmp = ScriptUpdater.meta.history.match(/(\S+)\s+(.*)$/);
  184. version = tmp[1];
  185. change = tmp[2];
  186. history[version] = typeof(history[version]) == 'undefined' ? new Array() : history[version];
  187. history[version].push(change);
  188. }
  189. for(var v in history) {
  190. html.push('<div style="margin-top:.75em;"><strong>v' + v + '</strong></div><ul>');
  191. for(var i = 0; i < history[v].length; i++)
  192. html.push('<li>' + history[v][i] + '</li>');
  193. html.push('</ul>');
  194. }
  195. html.push('</div>');
  196. }
  197. /*
  198. */
  199. html.push('<div id="ScriptUpdater' + ScriptUpdater.scriptId + 'Footer">');
  200. html.push('<input type="button" id="ScriptUpdater' + ScriptUpdater.scriptId + 'CloseButton" value="Close" style="background-image:url(');
  201. html.push(ScriptUpdater.icons.close);
  202. html.push(')"/><input type="button" id="ScriptUpdater' + ScriptUpdater.scriptId + 'BodyInstall');
  203. html.push(ScriptUpdater.scriptId);
  204. html.push('" value="Install" style="background-image:url(');
  205. html.push(ScriptUpdater.icons.install);
  206. html.push(');"/>');
  207. html.push('Check this script for updates ');
  208.  
  209. html.push('<select id="ScriptUpdater' + ScriptUpdater.scriptId + 'Interval"> \
  210. <option value="3600000">every hour </option>\
  211. <option value="21600000">every 6 hours </option>\
  212. <option value="86400000">every day </option>\
  213. <option value="604800000">every week </option>\
  214. <option value="0">never </option>\
  215. </select>');
  216. html.push('</div>');
  217. notice.innerHTML = html.join('');
  218. noticeWrapper.appendChild(notice);
  219. document.body.appendChild(noticeWrapper);
  220. ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'Close').addEventListener('click', ScriptUpdater.closeNotice, true);
  221. ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'CloseButton').addEventListener('click', ScriptUpdater.closeNotice, true);
  222. ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'BodyInstall' + ScriptUpdater.scriptId).addEventListener('click', function() {
  223. setTimeout(ScriptUpdater.closeNotice, 500);
  224. document.location = typeof(ScriptUpdater.installUrl) == 'string' ? ScriptUpdater.installUrl : 'http://userscripts.org/scripts/source/' + ScriptUpdater.scriptId + '.user.js';
  225. }, true);
  226. window.addEventListener('keyup', ScriptUpdater.keyUpHandler, true);
  227. // set current interval in selector
  228. var selector = ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'Interval');
  229. for(var i = 0; i < selector.options.length; i++) {
  230. if(selector.options[i].value.toString() == ScriptUpdater.getInterval().toString())
  231. selector.options[i].selected = true;
  232. }
  233. selector.addEventListener('change', function() {
  234. ScriptUpdater.setInterval(this.value);
  235. }, true);
  236. noticeWrapper.style.height = document.documentElement.clientHeigh + 'px';
  237. // $('#ScriptUpdater" + ScriptUpdater.scriptId + "Body')[0].style.marginTop = (unsafeWindow.scrollY + 125) + 'px';
  238. $('#ScriptUpdater' + ScriptUpdater.scriptId + 'Mask')[0].style.height = (unsafeWindow.scrollMaxY + unsafeWindow.innerHeight) + 'px';
  239. }
  240. },
  241. closeNotice:function() {
  242. document.body.removeChild(ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'BodyWrapper'));
  243. document.body.removeChild(ScriptUpdater.$('ScriptUpdater' + ScriptUpdater.scriptId + 'Mask'));
  244. window.removeEventListener('keyup', ScriptUpdater.keyUpHandler, true);
  245. },
  246. keyUpHandler:function (e) {
  247. if(e.keyCode == 27) { ScriptUpdater.closeNotice(); }
  248. },
  249. getVal:function(key) {
  250. key = 'ScriptUpdator.' + key;
  251. return eval(GM_getValue(key, ('({})')));
  252. },
  253. setVal:function(key, value) {
  254. key = 'ScriptUpdator.' + key;
  255. GM_setValue(key, uneval(value));
  256. },
  257. alreadyOffered:function(version) {
  258. var offers = ScriptUpdater.getOffers();
  259. if(offers.length == 0) {
  260. ScriptUpdater.addOffer(version);
  261. return true;
  262. }
  263. for(var i = 0; i < offers.length; i++)
  264. if(version.toString() == offers[i].toString()) { return true; }
  265. return false;
  266. },
  267. getOffers:function() {
  268. var offers = ScriptUpdater.getVal('versionsOfferedFor_' + ScriptUpdater.scriptId);
  269. return (typeof(offers) == 'undefined' || typeof(offers.length) == 'undefined' || typeof(offers.push) == 'undefined') ? new Array() : offers;
  270. },
  271. addOffer:function(version) {
  272. var offers = ScriptUpdater.getOffers();
  273. offers.push(version);
  274. ScriptUpdater.setVal('versionsOfferedFor_' + ScriptUpdater.scriptId, offers);
  275. },
  276. getInterval:function() {
  277. var interval = ScriptUpdater.getVal('interval_' + ScriptUpdater.scriptId);
  278. return (typeof(interval) == 'undefined' || !interval.toString().match(/^\d+$/)) ? 86400000 : parseInt(interval.toString());
  279. },
  280. setInterval:function(interval) {
  281. ScriptUpdater.setVal('interval_' + ScriptUpdater.scriptId, parseInt(interval));
  282. },
  283. getLastCheck:function() {
  284. var lastCheck = ScriptUpdater.getVal('lastCheck_' + ScriptUpdater.scriptId);
  285. return (typeof(lastCheck) == 'undefined' || !lastCheck.toString().match(/^\d+$/)) ? 0: parseInt(lastCheck.toString());
  286. },
  287. icons:{
  288. install:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALZSURBVBgZBcFLiFVlAADg7zzuPLzjzDjOMINMitIie5gF+UAkIZSgRQuXLZIWrY021dYIggJdJURElJsoqlWRYA9GshGFCNQeOjoTk6bjeOd5zzn/f07flzRNA459ObcHJ3cM9+1fq2prVa2qa+uh7mAZ9xCxiAV8iu9zgDqEvU9ODOx//dkxALBa1kNrZT202I2TZcVyEd28t+Lb66uHcTwHqEMYH+xJwNyDqJUk8oQsp7eV2tqbytJUK+OpyX5bhtojH07Pv58CxKoabOeEmuUy0al4UNDp0umysM5/KxG8eWbW/u1tj4+2xnKAWFUjG3tSqwWr3ShNEzmyjDQjk8gSaiRxyYUbiy7PduZzgFiW40P9mc56sFY00rSRpaQxkaVkGlmGJnNnqXDq7N9LOJYDhLLcNj7Y0uk2AjRkMZE2iGQaeZOqG2IrCmXY/s1rB+6nALEstk0M9VotG0lKliRSpEjw+YUjPjq3RxkKoSjEsoiQwvMnvusXQ09vK1VGUg1qjVrUqDWKUJoc3emVj3dbWeuEUJZLkEMoyrF2u0+aUEPD19OHNXVQ1kEZgy2bHrZzYq/l7qr766/m3VC0ub+SQyyLDXm7R56SpYlYJ0JdOvzYy2JTi3VUa8x35jwxecBKue7S7E+dXW+nI/nB42dGcWLPI1vdXmrcvBO1++iGUmxqtxb+UtVBqCtVrCwVy3Y/dNBKtZb+OjO1kMeyfA4vXLo6Y3E9t1I0qtjo6goxGB/cKtRRbGr/dmaNDEy4PHfe+etTd8vgSB6r6ukXD+3qf+ulfQDg6OnCJ7+8p6xL3VDaMfqofTuOuHhryrk/fl4tokPz7zRX8lhVM7fvdXx29qrhgX7Dg32G271OHv3dxg09entSvXnqmXcHJGm/6Ru/ad89dmrm9AdXIK9D+GLq4rXJqYvXtmEzNmMTNmGor6fV6utr6YxWfvjzR0P/vDGTh7GvAP4H2uh1wse2x/0AAAAASUVORK5CYII%3D",
  289. close:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIhSURBVDjLlZPrThNRFIWJicmJz6BWiYbIkYDEG0JbBiitDQgm0PuFXqSAtKXtpE2hNuoPTXwSnwtExd6w0pl2OtPlrphKLSXhx07OZM769qy19wwAGLhM1ddC184+d18QMzoq3lfsD3LZ7Y3XbE5DL6Atzuyilc5Ciyd7IHVfgNcDYTQ2tvDr5crn6uLSvX+Av2Lk36FFpSVENDe3OxDZu8apO5rROJDLo30+Nlvj5RnTlVNAKs1aCVFr7b4BPn6Cls21AWgEQlz2+Dl1h7IdA+i97A/geP65WhbmrnZZ0GIJpr6OqZqYAd5/gJpKox4Mg7pD2YoC2b0/54rJQuJZdm6Izcgma4TW1WZ0h+y8BfbyJMwBmSxkjw+VObNanp5h/adwGhaTXF4NWbLj9gEONyCmUZmd10pGgf1/vwcgOT3tUQE0DdicwIod2EmSbwsKE1P8QoDkcHPJ5YESjgBJkYQpIEZ2KEB51Y6y3ojvY+P8XEDN7uKS0w0ltA7QGCWHCxSWWpwyaCeLy0BkA7UXyyg8fIzDoWHeBaDN4tQdSvAVdU1Aok+nsNTipIEVnkywo/FHatVkBoIhnFisOBoZxcGtQd4B0GYJNZsDSiAEadUBCkstPtN3Avs2Msa+Dt9XfxoFSNYF/Bh9gP0bOqHLAm2WUF1YQskwrVFYPWkf3h1iXwbvqGfFPSGW9Eah8HSS9fuZDnS32f71m8KFY7xs/QZyu6TH2+2+FAAAAABJRU5ErkJggg%3D%3D",
  290. uso:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQCAYAAAAiYZ4HAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAh9JREFUeNp0krmLWnEQxyf7zLoajyIWXojIxkK0EiIGCRamCKQwEdIIgYQoQSR/wLY2goVVJGCa1BaL2liKBESFiOJFiMRb1xMVRbx+mfdA0RwDA4/3m+Mz3xmAf9hDNJ/P9zWXy935/f7A5eXlFfzPRCKROBgMfqvX62S5XBLabDbbh8M76zRYKpUqvF5vyGw2P+bz+cBisWCz2cB2u33wV2WFQvEoFArlW60WmUwmZLVakdFoRNxu9xd8Fp51UKlUWmS91ev11zweD5AZMAFmsxkgWhpDpsfKarVaE4lEqpVKhUynU4a73++TcrlMarUa6Xa7G7vd/u4QT93c3HzmcrlPSqUSiMVihrvX68F6vYZsNkvPcOFyuV5Uq9VuoVD4ztrv91wOhwMCgQAGgwEsFguYz+eMSyQSkMvlwGazqUAg8KnRaHSo4XA4Q9leYRdmHrpyJpMBehaDwQBCoRB2ux2gapRSqbymsP2PTqezsFqtz+6hpVIpprLRaGTw8BcgBVOo2WyOj8NbLJaP+Xx+k0gkCL00xGNEoJ2WOZlMznQ6nfVsFyaT6X273d4eAmkfj8ckHo+PNRrNSzrm4jRBq9XysDWF18Cg0OzpdPrO6XS+QRVvz6oj0nOch25NYrEYgxEOhxsymezpadyxA8p5HxUDXBTgSUA0Gv3pcDheI2LiNIE6fOAN/cKkK9RdUSwWkx6P5y0mZv+8ud8CDABidDMA4Sb2JAAAAABJRU5ErkJggg%3D%3D",
  291. },
  292.  
  293. };