// ==UserScript== // @name UNIFIED CHALLENGES CheckPlay Tool (Flickr) // @namespace http://www.flickr.com/groups/ // @description CheckPlay Tool for Flickr CHALLENGE groups // @date 01/06/2008 // @creator Kris Vangeel (http://flickr.com/kvgl69/) // original script for the Photo Face-Off challenge group // @contributor Andrew Dunn (http://flickr.com/andrewdunn/) // for changes in CYCheckPlay (Challenge You) // @contributor i_need_my_blankie (http://flickr.com/25084834@N03/) // for changes in FCCheckPlay (FRIENDLY Challenges) // @contributor Alesa Dam (http://flickr.com/alesadam/) // for making it generic, and adding features // @modified Jan. 20, 2011 // @version 1.1.1 // // @include http://www.flickr.com/groups/*/discuss* // @exclude http://www.flickr.com/groups/*/?search* // @match http://www.flickr.com/groups/*/discuss* // @include http://userscripts.org/* // @match http://userscripts.org/* // // @run-at document-end // // ==/UserScript== // // ChangeLog: http://www.flickr.com/groups/unified_checkplay/discuss/72157623882744032/ // Documentation: http://www.flickr.com/groups/unified_checkplay/discuss/72157623600133810/ // // -------------------------------------------------------------------- // // This is a Greasemonkey user script. // // To install, you need Greasemonkey: http://greasemonkey.mozdev.org/ // Then restart Firefox and revisit this script. // Under Tools, there will be a new menu item to "Install User Script". // Accept the default configuration and install. // // -------------------------------------------------------------------- // known issues: // - greasemonkey does not work on FF3.6 and up on 64bit windows // - in PIC-P-1, votes are 'lost' when a player changes his/her username // - in PIC-P-1, there is no vote checking yet // // desired features: // - provide a panel with all the challenges I'm a player in, over the different groups // - excludes may become invalid after a certain time // - some groups require the last challenger to start the voting process // => type "mustvote" shows it // - place a check box next to each photo, and create the vote for the user based on the chosen photo(s) // => dropped: photos without a link are ignored, voting that is in error difficult to anticipate, .. // - incorporate functions for admins: // . mark error as checked // - some challenges have predefined challengers (best of the best, MatchPoint, DUETOS) // . new state: instead of open/waiting, waitingForYou! // - support for different photo sizes: icon challenges // - add a 'check for medals' next to a player's entry // - try to use some CPU power of the pipes.yahoo.com servers :) // //--------------------- // If you have any suggestions for improvement, encounter bugs, have patches available , or you play in a // challenge group that is not supported, please FlickrMail us, and we will take care of it. // We try to keep this as UNIFIED as possible. // Contacts: // - Kate G. (http://www.flickr.com/photos/25084834@N03/) // - Alesa Dam (http://www.flickr.com/photos/alesadam/) // move the version check upfront // in case something goes really wrong, changes are that a version bump could still be accessible // don't place spaces around the '=' sign: incompatible with 'checkVersion' in older versions (function() { var CPtoolversion="V1.1.1"; var scriptNumber = 60303; // Greased MooTools: /* --- script: Core.js description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts. license: MIT-style license. copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/). authors: The MooTools production team (http://mootools.net/developers/) inspiration: - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) provides: [MooTools, Native, Hash.base, Array.each, $util] ... */ var MooTools = { 'version': '1.2.5dev', 'build': '168759f5904bfdaeafd6b1c0d1be16cd78b5d5c6' }; var Native = function(options){ options = options || {}; var name = options.name; var legacy = options.legacy; var protect = options.protect; var methods = options.implement; var generics = options.generics; var initialize = options.initialize; var afterImplement = options.afterImplement || function(){}; var object = initialize || legacy; generics = generics !== false; object.constructor = Native; object.$family = {name: 'native'}; if (legacy && initialize) object.prototype = legacy.prototype; if (!object.prototype) object.prototype = {}; object.prototype.constructor = object; if (name){ var family = name.toLowerCase(); object.prototype.$family = {name: family}; Native.typize(object, family); } var add = function(obj, name, method, force){ if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method; if (generics) Native.genericize(obj, name, protect); afterImplement.call(obj, name, method); return obj; }; object.alias = function(a1, a2, a3){ if (typeof a1 == 'string'){ var pa1 = this.prototype[a1]; if ((a1 = pa1)) return add(this, a2, a1, a3); } for (var a in a1) this.alias(a, a1[a], a2); return this; }; object.implement = function(a1, a2, a3){ if (typeof a1 == 'string') return add(this, a1, a2, a3); for (var p in a1) add(this, p, a1[p], a2); return this; }; if (methods) object.implement(methods); return object; }; Native.genericize = function(object, property, check){ if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){ var args = Array.prototype.slice.call(arguments); return object.prototype[property].apply(args.shift(), args); }; }; Native.implement = function(objects, properties){ for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties); }; Native.typize = function(object, family){ if (!object.type) object.type = function(item){ return ($type(item) === family); }; }; (function(){ var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String}; for (var n in natives) new Native({name: n, initialize: natives[n], protect: true}); var types = {'boolean': Boolean, 'native': Native, 'object': Object}; for (var t in types) Native.typize(types[t], t); var generics = { 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"], 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"] }; for (var g in generics){ for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true); } })(); var Hash = new Native({ name: 'Hash', initialize: function(object){ if ($type(object) == 'hash') object = $unlink(object.getClean()); for (var key in object) this[key] = object[key]; return this; } }); Hash.implement({ forEach: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this); } }, getClean: function(){ var clean = {}; for (var key in this){ if (this.hasOwnProperty(key)) clean[key] = this[key]; } return clean; }, getLength: function(){ var length = 0; for (var key in this){ if (this.hasOwnProperty(key)) length++; } return length; } }); Hash.alias('forEach', 'each'); Array.implement({ forEach: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this); } }); Array.alias('forEach', 'each'); function $A(iterable){ if (iterable.item){ var l = iterable.length, array = new Array(l); while (l--) array[l] = iterable[l]; return array; } return Array.prototype.slice.call(iterable); }; function $arguments(i){ return function(){ return arguments[i]; }; }; function $chk(obj){ return !!(obj || obj === 0); }; function $clear(timer){ clearTimeout(timer); clearInterval(timer); return null; }; function $defined(obj){ return (obj != undefined); }; function $each(iterable, fn, bind){ var type = $type(iterable); ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind); }; function $empty(){}; function $extend(original, extended){ for (var key in (extended || {})) original[key] = extended[key]; return original; }; function $H(object){ return new Hash(object); }; function $lambda(value){ return ($type(value) == 'function') ? value : function(){ return value; }; }; function $merge(){ var args = Array.slice(arguments); args.unshift({}); return $mixin.apply(null, args); }; function $mixin(mix){ for (var i = 1, l = arguments.length; i < l; i++){ var object = arguments[i]; if ($type(object) != 'object') continue; for (var key in object){ var op = object[key], mp = mix[key]; mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op); } } return mix; }; function $pick(){ for (var i = 0, l = arguments.length; i < l; i++){ if (arguments[i] != undefined) return arguments[i]; } return null; }; function $random(min, max){ return Math.floor(Math.random() * (max - min + 1) + min); }; function $splat(obj){ var type = $type(obj); return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : []; }; var $time = Date.now || function(){ return +new Date; }; function $try(){ for (var i = 0, l = arguments.length; i < l; i++){ try { return arguments[i](); } catch(e){} } return null; }; function $type(obj){ if (obj == undefined) return false; if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name; if (obj.nodeName){ switch (obj.nodeType){ case 1: return 'element'; case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; } } else if (typeof obj.length == 'number'){ if (obj.callee) return 'arguments'; else if (obj.item) return 'collection'; } return typeof obj; }; function $unlink(object){ var unlinked; switch ($type(object)){ case 'object': unlinked = {}; for (var p in object) unlinked[p] = $unlink(object[p]); break; case 'hash': unlinked = new Hash(object); break; case 'array': unlinked = []; for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]); break; default: return object; } return unlinked; }; /* --- script: Browser.js description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash. license: MIT-style license. requires: - /Native - /$util provides: [Browser, Window, Document, $exec] ... */ var Browser = $merge({ Engine: {name: 'unknown', version: 0}, Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()}, Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}, Plugins: {}, Engines: { presto: function(){ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }, trident: function(){ return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }, webkit: function(){ return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419); }, gecko: function(){ return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); } } }, Browser || {}); Browser.Platform[Browser.Platform.name] = true; Browser.detect = function(){ for (var engine in this.Engines){ var version = this.Engines[engine](); if (version){ this.Engine = {name: engine, version: version}; this.Engine[engine] = this.Engine[engine + version] = true; break; } } return {name: engine, version: version}; }; Browser.detect(); Browser.Request = function(){ return $try(function(){ return new XMLHttpRequest(); }, function(){ return new ActiveXObject('MSXML2.XMLHTTP'); }, function(){ return new ActiveXObject('Microsoft.XMLHTTP'); }); }; Browser.Features.xhr = !!(Browser.Request()); Browser.Plugins.Flash = (function(){ var version = ($try(function(){ return navigator.plugins['Shockwave Flash'].description; }, function(){ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); }) || '0 r0').match(/\d+/g); return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; })(); function $exec(text){ if (!text) return text; if (window.execScript){ window.execScript(text); } else { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text; document.head.appendChild(script); document.head.removeChild(script); } return text; }; Native.UID = 1; var $uid = (Browser.Engine.trident) ? function(item){ return (item.uid || (item.uid = [Native.UID++]))[0]; } : function(item){ return item.uid || (item.uid = Native.UID++); }; var Window = new Native({ name: 'Window', legacy: (Browser.Engine.trident) ? null: window.Window, initialize: function(win){ $uid(win); if (!win.Element){ win.Element = $empty; if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2 win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {}; } win.document.window = win; return $extend(win, Window.Prototype); }, afterImplement: function(property, value){ window[property] = Window.Prototype[property] = value; } }); Window.Prototype = {$family: {name: 'window'}}; new Window(window); var Document = new Native({ name: 'Document', legacy: (Browser.Engine.trident) ? null: window.Document, initialize: function(doc){ $uid(doc); doc.head = doc.getElementsByTagName('head')[0]; doc.html = doc.getElementsByTagName('html')[0]; if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){ doc.execCommand("BackgroundImageCache", false, true); }); if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){ doc.window.detachEvent('onunload', arguments.callee); doc.head = doc.html = doc.window = null; }); return $extend(doc, Document.Prototype); }, afterImplement: function(property, value){ document[property] = Document.Prototype[property] = value; } }); Document.Prototype = {$family: {name: 'document'}}; new Document(document); /* --- script: Array.js description: Contains Array Prototypes like each, contains, and erase. license: MIT-style license. requires: - /$util - /Array.each provides: [Array] ... */ Array.implement({ every: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (!fn.call(bind, this[i], i, this)) return false; } return true; }, filter: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) results.push(this[i]); } return results; }, clean: function(){ return this.filter($defined); }, indexOf: function(item, from){ var len = this.length; for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ if (this[i] === item) return i; } return -1; }, map: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this); return results; }, some: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) return true; } return false; }, associate: function(keys){ var obj = {}, length = Math.min(this.length, keys.length); for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; return obj; }, link: function(object){ var result = {}; for (var i = 0, l = this.length; i < l; i++){ for (var key in object){ if (object[key](this[i])){ result[key] = this[i]; delete object[key]; break; } } } return result; }, contains: function(item, from){ return this.indexOf(item, from) != -1; }, extend: function(array){ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); return this; }, getLast: function(){ return (this.length) ? this[this.length - 1] : null; }, getRandom: function(){ return (this.length) ? this[$random(0, this.length - 1)] : null; }, include: function(item){ if (!this.contains(item)) this.push(item); return this; }, combine: function(array){ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); return this; }, erase: function(item){ for (var i = this.length; i--; i){ if (this[i] === item) this.splice(i, 1); } return this; }, empty: function(){ this.length = 0; return this; }, flatten: function(){ var array = []; for (var i = 0, l = this.length; i < l; i++){ var type = $type(this[i]); if (!type) continue; array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]); } return array; }, hexToRgb: function(array){ if (this.length != 3) return null; var rgb = this.map(function(value){ if (value.length == 1) value += value; return value.toInt(16); }); return (array) ? rgb : 'rgb(' + rgb + ')'; }, rgbToHex: function(array){ if (this.length < 3) return null; if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; var hex = []; for (var i = 0; i < 3; i++){ var bit = (this[i] - 0).toString(16); hex.push((bit.length == 1) ? '0' + bit : bit); } return (array) ? hex : '#' + hex.join(''); } }); /* --- script: String.js description: Contains String Prototypes like camelCase, capitalize, test, and toInt. license: MIT-style license. requires: - /Native provides: [String] ... */ String.implement({ test: function(regex, params){ return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this); }, contains: function(string, separator){ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; }, trim: function(){ return this.replace(/^\s+|\s+$/g, ''); }, clean: function(){ return this.replace(/\s+/g, ' ').trim(); }, camelCase: function(){ return this.replace(/-\D/g, function(match){ return match.charAt(1).toUpperCase(); }); }, hyphenate: function(){ return this.replace(/[A-Z]/g, function(match){ return ('-' + match.charAt(0).toLowerCase()); }); }, capitalize: function(){ return this.replace(/\b[a-z]/g, function(match){ return match.toUpperCase(); }); }, escapeRegExp: function(){ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); }, toInt: function(base){ return parseInt(this, base || 10); }, toFloat: function(){ return parseFloat(this); }, hexToRgb: function(array){ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return (hex) ? hex.slice(1).hexToRgb(array) : null; }, rgbToHex: function(array){ var rgb = this.match(/\d{1,3}/g); return (rgb) ? rgb.rgbToHex(array) : null; }, stripScripts: function(option){ var scripts = ''; var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){ scripts += arguments[1] + '\n'; return ''; }); if (option === true) $exec(scripts); else if ($type(option) == 'function') option(scripts, text); return text; }, substitute: function(object, regexp){ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ if (match.charAt(0) == '\\') return match.slice(1); return (object[name] != undefined) ? object[name] : ''; }); } }); /* --- script: Function.js description: Contains Function Prototypes like create, bind, pass, and delay. license: MIT-style license. requires: - /Native - /$util provides: [Function] ... */ Function.implement({ extend: function(properties){ for (var property in properties) this[property] = properties[property]; return this; }, create: function(options){ var self = this; options = options || {}; return function(event){ var args = options.arguments; args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0); if (options.event) args = [event || window.event].extend(args); var returns = function(){ return self.apply(options.bind || null, args); }; if (options.delay) return setTimeout(returns, options.delay); if (options.periodical) return setInterval(returns, options.periodical); if (options.attempt) return $try(returns); return returns(); }; }, run: function(args, bind){ return this.apply(bind, $splat(args)); }, pass: function(args, bind){ return this.create({bind: bind, arguments: args}); }, bind: function(bind, args){ return this.create({bind: bind, arguments: args}); }, bindWithEvent: function(bind, args){ return this.create({bind: bind, arguments: args, event: true}); }, attempt: function(args, bind){ return this.create({bind: bind, arguments: args, attempt: true})(); }, delay: function(delay, bind, args){ return this.create({bind: bind, arguments: args, delay: delay})(); }, periodical: function(periodical, bind, args){ return this.create({bind: bind, arguments: args, periodical: periodical})(); } }); /* --- script: Number.js description: Contains Number Prototypes like limit, round, times, and ceil. license: MIT-style license. requires: - /Native - /$util provides: [Number] ... */ Number.implement({ limit: function(min, max){ return Math.min(max, Math.max(min, this)); }, round: function(precision){ precision = Math.pow(10, precision || 0); return Math.round(this * precision) / precision; }, times: function(fn, bind){ for (var i = 0; i < this; i++) fn.call(bind, i, this); }, toFloat: function(){ return parseFloat(this); }, toInt: function(base){ return parseInt(this, base || 10); } }); Number.alias('times', 'each'); (function(math){ var methods = {}; math.each(function(name){ if (!Number[name]) methods[name] = function(){ return Math[name].apply(null, [this].concat($A(arguments))); }; }); Number.implement(methods); })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); /* --- script: Hash.js description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. license: MIT-style license. requires: - /Hash.base provides: [Hash] ... */ Hash.implement({ has: Object.prototype.hasOwnProperty, keyOf: function(value){ for (var key in this){ if (this.hasOwnProperty(key) && this[key] === value) return key; } return null; }, hasValue: function(value){ return (Hash.keyOf(this, value) !== null); }, extend: function(properties){ Hash.each(properties || {}, function(value, key){ Hash.set(this, key, value); }, this); return this; }, combine: function(properties){ Hash.each(properties || {}, function(value, key){ Hash.include(this, key, value); }, this); return this; }, erase: function(key){ if (this.hasOwnProperty(key)) delete this[key]; return this; }, get: function(key){ return (this.hasOwnProperty(key)) ? this[key] : null; }, set: function(key, value){ if (!this[key] || this.hasOwnProperty(key)) this[key] = value; return this; }, empty: function(){ Hash.each(this, function(value, key){ delete this[key]; }, this); return this; }, include: function(key, value){ if (this[key] == undefined) this[key] = value; return this; }, map: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ results.set(key, fn.call(bind, value, key, this)); }, this); return results; }, filter: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ if (fn.call(bind, value, key, this)) results.set(key, value); }, this); return results; }, every: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false; } return true; }, some: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true; } return false; }, getKeys: function(){ var keys = []; Hash.each(this, function(value, key){ keys.push(key); }); return keys; }, getValues: function(){ var values = []; Hash.each(this, function(value){ values.push(value); }); return values; }, toQueryString: function(base){ var queryString = []; Hash.each(this, function(value, key){ if (base) key = base + '[' + key + ']'; var result; switch ($type(value)){ case 'object': result = Hash.toQueryString(value, key); break; case 'array': var qs = {}; value.each(function(val, i){ qs[i] = val; }); result = Hash.toQueryString(qs, key); break; default: result = key + '=' + encodeURIComponent(value); } if (value != undefined) queryString.push(result); }); return queryString.join('&'); } }); Hash.alias({keyOf: 'indexOf', hasValue: 'contains'}); /* --- script: Element.js description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. license: MIT-style license. requires: - /Window - /Document - /Array - /String - /Function - /Number - /Hash provides: [Element, Elements, $, $$, Iframe] ... */ var Element = new Native({ name: 'Element', legacy: window.Element, initialize: function(tag, props){ var konstructor = Element.Constructors.get(tag); if (konstructor) return konstructor(props); if (typeof tag == 'string') return document.newElement(tag, props); return document.id(tag).set(props); }, afterImplement: function(key, value){ Element.Prototype[key] = value; if (Array[key]) return; Elements.implement(key, function(){ var items = [], elements = true; for (var i = 0, j = this.length; i < j; i++){ var returns = this[i][key].apply(this[i], arguments); items.push(returns); if (elements) elements = ($type(returns) == 'element'); } return (elements) ? new Elements(items) : items; }); } }); Element.Prototype = {$family: {name: 'element'}}; Element.Constructors = new Hash; var IFrame = new Native({ name: 'IFrame', generics: false, initialize: function(){ var params = Array.link(arguments, {properties: Object.type, iframe: $defined}); var props = params.properties || {}; var iframe = document.id(params.iframe); var onload = props.onload || $empty; delete props.onload; props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time()); iframe = new Element(iframe || 'iframe', props); var onFrameLoad = function(){ var host = $try(function(){ return iframe.contentWindow.location.host; }); if (!host || host == window.location.host){ var win = new Window(iframe.contentWindow); new Document(iframe.contentWindow.document); if(!win.Element.prototype) win.Element.prototype = {}; $extend(win.Element.prototype, Element.Prototype); } onload.call(iframe.contentWindow, iframe.contentWindow.document); }; var contentWindow = $try(function(){ return iframe.contentWindow; }); ((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad); return iframe; } }); var Elements = new Native({ initialize: function(elements, options){ options = $extend({ddup: true, cash: true}, options); elements = elements || []; if (options.ddup || options.cash){ var uniques = {}, returned = []; for (var i = 0, l = elements.length; i < l; i++){ var el = document.id(elements[i], !options.cash); if (options.ddup){ if (uniques[el.uid]) continue; uniques[el.uid] = true; } if (el) returned.push(el); } elements = returned; } return (options.cash) ? $extend(elements, this) : elements; } }); Elements.implement({ filter: function(filter, bind){ if (!filter) return this; return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){ return item.match(filter); } : filter, bind)); } }); Document.implement({ newElement: function(tag, props){ if (Browser.Engine.trident && props){ ['name', 'type', 'checked'].each(function(attribute){ if (!props[attribute]) return; tag += ' ' + attribute + '="' + props[attribute] + '"'; if (attribute != 'checked') delete props[attribute]; }); tag = '<' + tag + '>'; } return document.id(this.createElement(tag)).set(props); }, newTextNode: function(text){ return this.createTextNode(text); }, getDocument: function(){ return this; }, getWindow: function(){ return this.window; }, id: (function(){ var types = { string: function(id, nocash, doc){ id = doc.getElementById(id); return (id) ? types.element(id, nocash) : null; }, element: function(el, nocash){ $uid(el); if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ var proto = Element.Prototype; for (var p in proto) el[p] = proto[p]; }; return el; }, object: function(obj, nocash, doc){ if (obj.toElement) return types.element(obj.toElement(doc), nocash); return null; } }; types.textnode = types.whitespace = types.window = types.document = $arguments(0); return function(el, nocash, doc){ if (el && el.$family && el.uid) return el; var type = $type(el); return (types[type]) ? types[type](el, nocash, doc || document) : null; }; })() }); if (window.$ == null) Window.implement({ $: function(el, nc){ return document.id(el, nc, this.document); } }); Window.implement({ $$: function(selector){ if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector); var elements = []; var args = Array.flatten(arguments); for (var i = 0, l = args.length; i < l; i++){ var item = args[i]; switch ($type(item)){ case 'element': elements.push(item); break; case 'string': elements.extend(this.document.getElements(item, true)); } } return new Elements(elements); }, getDocument: function(){ return this.document; }, getWindow: function(){ return this; } }); Native.implement([Element, Document], { getElement: function(selector, nocash){ return document.id(this.getElements(selector, true)[0] || null, nocash); }, getElements: function(tags, nocash){ tags = tags.split(','); var elements = []; var ddup = (tags.length > 1); tags.each(function(tag){ var partial = this.getElementsByTagName(tag.trim()); (ddup) ? elements.extend(partial) : elements = partial; }, this); return new Elements(elements, {ddup: ddup, cash: !nocash}); } }); (function(){ var collected = {}, storage = {}; var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'}; var get = function(uid){ return (storage[uid] || (storage[uid] = {})); }; var clean = function(item, retain){ if (!item) return; var uid = item.uid; if (Browser.Engine.trident){ if (item.clearAttributes){ var clone = retain && item.cloneNode(false); item.clearAttributes(); if (clone) item.mergeAttributes(clone); } else if (item.removeEvents){ item.removeEvents(); } if ((/object/i).test(item.tagName)){ for (var p in item){ if (typeof item[p] == 'function') item[p] = $empty; } Element.dispose(item); } } if (!uid) return; collected[uid] = storage[uid] = null; }; var purge = function(){ Hash.each(collected, clean); if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean); if (window.CollectGarbage) CollectGarbage(); collected = storage = null; }; var walk = function(element, walk, start, match, all, nocash){ var el = element[start || walk]; var elements = []; while (el){ if (el.nodeType == 1 && (!match || Element.match(el, match))){ if (!all) return document.id(el, nocash); elements.push(el); } el = el[walk]; } return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null; }; var attributes = { 'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'defaultValue': 'defaultValue', 'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent' }; var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer']; var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']; bools = bools.associate(bools); Hash.extend(attributes, bools); Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase))); var inserters = { before: function(context, element){ if (element.parentNode) element.parentNode.insertBefore(context, element); }, after: function(context, element){ if (!element.parentNode) return; var next = element.nextSibling; (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context); }, bottom: function(context, element){ element.appendChild(context); }, top: function(context, element){ var first = element.firstChild; (first) ? element.insertBefore(context, first) : element.appendChild(context); } }; inserters.inside = inserters.bottom; Hash.each(inserters, function(inserter, where){ where = where.capitalize(); Element.implement('inject' + where, function(el){ inserter(this, document.id(el, true)); return this; }); Element.implement('grab' + where, function(el){ inserter(document.id(el, true), this); return this; }); }); Element.implement({ set: function(prop, value){ switch ($type(prop)){ case 'object': for (var p in prop) this.set(p, prop[p]); break; case 'string': var property = Element.Properties.get(prop); (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value); } return this; }, get: function(prop){ var property = Element.Properties.get(prop); return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop); }, erase: function(prop){ var property = Element.Properties.get(prop); (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); return this; }, setProperty: function(attribute, value){ var key = attributes[attribute]; if (value == undefined) return this.removeProperty(attribute); if (key && bools[attribute]) value = !!value; (key) ? this[key] = value : this.setAttribute(attribute, '' + value); return this; }, setProperties: function(attributes){ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); return this; }, getProperty: function(attribute){ var key = attributes[attribute]; var value = (key) ? this[key] : this.getAttribute(attribute, 2); return (bools[attribute]) ? !!value : (key) ? value : value || null; }, getProperties: function(){ var args = $A(arguments); return args.map(this.getProperty, this).associate(args); }, removeProperty: function(attribute){ var key = attributes[attribute]; (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute); return this; }, removeProperties: function(){ Array.each(arguments, this.removeProperty, this); return this; }, hasClass: function(className){ return this.className.contains(className, ' '); }, addClass: function(className){ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); return this; }, removeClass: function(className){ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); return this; }, toggleClass: function(className){ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); }, adopt: function(){ Array.flatten(arguments).each(function(element){ element = document.id(element, true); if (element) this.appendChild(element); }, this); return this; }, appendText: function(text, where){ return this.grab(this.getDocument().newTextNode(text), where); }, grab: function(el, where){ inserters[where || 'bottom'](document.id(el, true), this); return this; }, inject: function(el, where){ inserters[where || 'bottom'](this, document.id(el, true)); return this; }, replaces: function(el){ el = document.id(el, true); el.parentNode.replaceChild(this, el); return this; }, wraps: function(el, where){ el = document.id(el, true); return this.replaces(el).grab(el, where); }, getPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, false, nocash); }, getAllPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, true, nocash); }, getNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, false, nocash); }, getAllNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, true, nocash); }, getFirst: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, false, nocash); }, getLast: function(match, nocash){ return walk(this, 'previousSibling', 'lastChild', match, false, nocash); }, getParent: function(match, nocash){ return walk(this, 'parentNode', null, match, false, nocash); }, getParents: function(match, nocash){ return walk(this, 'parentNode', null, match, true, nocash); }, getSiblings: function(match, nocash){ return this.getParent().getChildren(match, nocash).erase(this); }, getChildren: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, true, nocash); }, getWindow: function(){ return this.ownerDocument.window; }, getDocument: function(){ return this.ownerDocument; }, getElementById: function(id, nocash){ var el = this.ownerDocument.getElementById(id); if (!el) return null; for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ if (!parent) return null; } return document.id(el, nocash); }, getSelected: function(){ return new Elements($A(this.options).filter(function(option){ return option.selected; })); }, getComputedStyle: function(property){ if (this.currentStyle) return this.currentStyle[property.camelCase()]; var computed = this.getDocument().defaultView.getComputedStyle(this, null); return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null; }, toQueryString: function(){ var queryString = []; this.getElements('input, select, textarea', true).each(function(el){ if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return; var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){ return opt.value; }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value; $splat(value).each(function(val){ if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val)); }); }); return queryString.join('&'); }, clone: function(contents, keepid){ contents = contents !== false; var clone = this.cloneNode(contents); var clean = function(node, element){ if (!keepid) node.removeAttribute('id'); if (Browser.Engine.trident){ node.clearAttributes(); node.mergeAttributes(element); node.removeAttribute('uid'); if (node.options){ var no = node.options, eo = element.options; for (var j = no.length; j--;) no[j].selected = eo[j].selected; } } var prop = props[element.tagName.toLowerCase()]; if (prop && element[prop]) node[prop] = element[prop]; }; if (contents){ var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); for (var i = ce.length; i--;) clean(ce[i], te[i]); } clean(clone, this); return document.id(clone); }, destroy: function(){ Element.empty(this); Element.dispose(this); clean(this, true); return null; }, empty: function(){ $A(this.childNodes).each(function(node){ Element.destroy(node); }); return this; }, dispose: function(){ return (this.parentNode) ? this.parentNode.removeChild(this) : this; }, hasChild: function(el){ el = document.id(el, true); if (!el) return false; if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el); return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16); }, match: function(tag){ return (!tag || (tag == this) || (Element.get(this, 'tag') == tag)); } }); Native.implement([Element, Window, Document], { addListener: function(type, fn){ if (type == 'unload'){ var old = fn, self = this; fn = function(){ self.removeListener('unload', fn); old(); }; } else { collected[this.uid] = this; } if (this.addEventListener) this.addEventListener(type, fn, false); else this.attachEvent('on' + type, fn); return this; }, removeListener: function(type, fn){ if (this.removeEventListener) this.removeEventListener(type, fn, false); else this.detachEvent('on' + type, fn); return this; }, retrieve: function(property, dflt){ var storage = get(this.uid), prop = storage[property]; if (dflt != undefined && prop == undefined) prop = storage[property] = dflt; return $pick(prop); }, store: function(property, value){ var storage = get(this.uid); storage[property] = value; return this; }, eliminate: function(property){ var storage = get(this.uid); delete storage[property]; return this; } }); window.addListener('unload', purge); })(); Element.Properties = new Hash; Element.Properties.style = { set: function(style){ this.style.cssText = style; }, get: function(){ return this.style.cssText; }, erase: function(){ this.style.cssText = ''; } }; Element.Properties.tag = { get: function(){ return this.tagName.toLowerCase(); } }; Element.Properties.html = (function(){ var wrapper = document.createElement('div'); var translations = { table: [1, '<table>', '</table>'], select: [1, '<select>', '</select>'], tbody: [2, '<table><tbody>', '</tbody></table>'], tr: [3, '<table><tbody><tr>', '</tr></tbody></table>'] }; translations.thead = translations.tfoot = translations.tbody; var html = { set: function(){ var html = Array.flatten(arguments).join(''); var wrap = Browser.Engine.trident && translations[this.get('tag')]; if (wrap){ var first = wrapper; first.innerHTML = wrap[1] + html + wrap[2]; for (var i = wrap[0]; i--;) first = first.firstChild; this.empty().adopt(first.childNodes); } else { this.innerHTML = html; } } }; html.erase = html.set; return html; })(); if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = { get: function(){ if (this.innerText) return this.innerText; var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body); var text = temp.innerText; temp.destroy(); return text; } }; /* --- script: Element.Style.js description: Contains methods for interacting with the styles of Elements in a fashionable way. license: MIT-style license. requires: - /Element provides: [Element.Style] ... */ Element.Properties.styles = {set: function(styles){ this.setStyles(styles); }}; Element.Properties.opacity = { set: function(opacity, novisibility){ if (!novisibility){ if (opacity == 0){ if (this.style.visibility != 'hidden') this.style.visibility = 'hidden'; } else { if (this.style.visibility != 'visible') this.style.visibility = 'visible'; } } if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; this.style.opacity = opacity; this.store('opacity', opacity); }, get: function(){ return this.retrieve('opacity', 1); } }; Element.implement({ setOpacity: function(value){ return this.set('opacity', value, true); }, getOpacity: function(){ return this.get('opacity'); }, setStyle: function(property, value){ switch (property){ case 'opacity': return this.set('opacity', parseFloat(value)); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); if ($type(value) != 'string'){ var map = (Element.Styles.get(property) || '@').split(' '); value = $splat(value).map(function(val, i){ if (!map[i]) return ''; return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; }).join(' '); } else if (value == String(Number(value))){ value = Math.round(value); } this.style[property] = value; return this; }, getStyle: function(property){ switch (property){ case 'opacity': return this.get('opacity'); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); var result = this.style[property]; if (!$chk(result)){ result = []; for (var style in Element.ShortStyles){ if (property != style) continue; for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); return result.join(' '); } result = this.getComputedStyle(property); } if (result){ result = String(result); var color = result.match(/rgba?\([\d\s,]+\)/); if (color) result = result.replace(color[0], color[0].rgbToHex()); } if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){ if (property.test(/^(height|width)$/)){ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; values.each(function(value){ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); }, this); return this['offset' + property.capitalize()] - size + 'px'; } if ((Browser.Engine.presto) && String(result).test('px')) return result; if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; } return result; }, setStyles: function(styles){ for (var style in styles) this.setStyle(style, styles[style]); return this; }, getStyles: function(){ var result = {}; Array.flatten(arguments).each(function(key){ result[key] = this.getStyle(key); }, this); return result; } }); Element.Styles = new Hash({ left: '@px', top: '@px', bottom: '@px', right: '@px', width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' }); Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ var Short = Element.ShortStyles; var All = Element.Styles; ['margin', 'padding'].each(function(style){ var sd = style + direction; Short[style][sd] = All[sd] = '@px'; }); var bd = 'border' + direction; Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; Short[bd] = {}; Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; }); /* --- script: Element.Dimensions.js description: Contains methods to work with size, scroll, or positioning of Elements and the window object. license: MIT-style license. credits: - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). requires: - /Element provides: [Element.Dimensions] ... */ (function(){ Element.implement({ scrollTo: function(x, y){ if (isBody(this)){ this.getWindow().scrollTo(x, y); } else { this.scrollLeft = x; this.scrollTop = y; } return this; }, getSize: function(){ if (isBody(this)) return this.getWindow().getSize(); return {x: this.offsetWidth, y: this.offsetHeight}; }, getScrollSize: function(){ if (isBody(this)) return this.getWindow().getScrollSize(); return {x: this.scrollWidth, y: this.scrollHeight}; }, getScroll: function(){ if (isBody(this)) return this.getWindow().getScroll(); return {x: this.scrollLeft, y: this.scrollTop}; }, getScrolls: function(){ var element = this, position = {x: 0, y: 0}; while (element && !isBody(element)){ position.x += element.scrollLeft; position.y += element.scrollTop; element = element.parentNode; } return position; }, getOffsetParent: function(){ var element = this; if (isBody(element)) return null; if (!Browser.Engine.trident) return element.offsetParent; while ((element = element.parentNode) && !isBody(element)){ if (styleString(element, 'position') != 'static') return element; } return null; }, getOffsets: function(){ if (this.getBoundingClientRect){ var bound = this.getBoundingClientRect(), html = document.id(this.getDocument().documentElement), //htmlScroll = html.getScroll(), htmlScroll = { x: 0, y: 0 }, elemScrolls = this.getScrolls(), elemScroll = this.getScroll(), isFixed = (styleString(this, 'position') == 'fixed'); return { x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, y: bound.top.toInt() + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; if (Browser.Engine.gecko){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.Engine.webkit){ position.x += leftBorder(element); position.y += topBorder(element); } element = element.offsetParent; } if (Browser.Engine.gecko && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } return position; }, getPosition: function(relative){ if (isBody(this)) return {x: 0, y: 0}; var offset = this.getOffsets(), scroll = this.getScrolls(); var position = { x: offset.x - scroll.x, y: offset.y - scroll.y }; var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0}; return {x: position.x - relativePosition.x, y: position.y - relativePosition.y}; }, getCoordinates: function(element){ if (isBody(this)) return this.getWindow().getCoordinates(); var position = this.getPosition(element), size = this.getSize(); var obj = { left: position.x, top: position.y, width: size.x, height: size.y }; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; }, computePosition: function(obj){ return { left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top') }; }, setPosition: function(obj){ return this.setStyles(this.computePosition(obj)); } }); Native.implement([Document, Window], { getSize: function(){ if (Browser.Engine.presto || Browser.Engine.webkit){ var win = this.getWindow(); return {x: win.innerWidth, y: win.innerHeight}; } var doc = getCompatElement(this); return {x: doc.clientWidth, y: doc.clientHeight}; }, getScroll: function(){ var win = this.getWindow(), doc = getCompatElement(this); return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; }, getScrollSize: function(){ var doc = getCompatElement(this), min = this.getSize(); return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)}; }, getPosition: function(){ return {x: 0, y: 0}; }, getCoordinates: function(){ var size = this.getSize(); return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; } }); // private methods var styleString = Element.getComputedStyle; function styleNumber(element, style){ return styleString(element, style).toInt() || 0; }; function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; }; function topBorder(element){ return styleNumber(element, 'border-top-width'); }; function leftBorder(element){ return styleNumber(element, 'border-left-width'); }; function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); }; function getCompatElement(element){ var doc = element.getDocument(); return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; }; })(); //aliases Element.alias('setPosition', 'position'); //compatability Native.implement([Window, Document, Element], { getHeight: function(){ return this.getSize().y; }, getWidth: function(){ return this.getSize().x; }, getScrollTop: function(){ return this.getScroll().y; }, getScrollLeft: function(){ return this.getScroll().x; }, getScrollHeight: function(){ return this.getScrollSize().y; }, getScrollWidth: function(){ return this.getScrollSize().x; }, getTop: function(){ return this.getPosition().y; }, getLeft: function(){ return this.getPosition().x; } }); /* --- script: Selectors.js description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors. license: MIT-style license. requires: - /Element provides: [Selectors] ... */ Native.implement([Document, Element], { getElements: function(expression, nocash){ expression = expression.split(','); var items, local = {}; for (var i = 0, l = expression.length; i < l; i++){ var selector = expression[i], elements = Selectors.Utils.search(this, selector, local); if (i != 0 && elements.item) elements = $A(elements); items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements); } return new Elements(items, {ddup: (expression.length > 1), cash: !nocash}); } }); Element.implement({ match: function(selector){ if (!selector || (selector == this)) return true; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false; var parsed = Selectors.Utils.parseSelector(selector); return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true; } }); var Selectors = {Cache: {nth: {}, parsed: {}}}; Selectors.RegExps = { id: (/#([\w-]+)/), tag: (/^(\w+|\*)/), quick: (/^(\w+|\*)$/), splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g), combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g) }; Selectors.Utils = { chk: function(item, uniques){ if (!uniques) return true; var uid = $uid(item); if (!uniques[uid]) return uniques[uid] = true; return false; }, parseNthArgument: function(argument){ if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument]; var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/); if (!parsed) return false; var inta = parseInt(parsed[1], 10); var a = (inta || inta === 0) ? inta : 1; var special = parsed[2] || false; var b = parseInt(parsed[3], 10) || 0; if (a != 0){ b--; while (b < 1) b += a; while (b >= a) b -= a; } else { a = b; special = 'index'; } switch (special){ case 'n': parsed = {a: a, b: b, special: 'n'}; break; case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break; case 'even': parsed = {a: 2, b: 1, special: 'n'}; break; case 'first': parsed = {a: 0, special: 'index'}; break; case 'last': parsed = {special: 'last-child'}; break; case 'only': parsed = {special: 'only-child'}; break; default: parsed = {a: (a - 1), special: 'index'}; } return Selectors.Cache.nth[argument] = parsed; }, parseSelector: function(selector){ if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector]; var m, parsed = {classes: [], pseudos: [], attributes: []}; while ((m = Selectors.RegExps.combined.exec(selector))){ var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7]; if (cn){ parsed.classes.push(cn); } else if (pn){ var parser = Selectors.Pseudo.get(pn); if (parser) parsed.pseudos.push({parser: parser, argument: pa}); else parsed.attributes.push({name: pn, operator: '=', value: pa}); } else if (an){ parsed.attributes.push({name: an, operator: ao, value: av}); } } if (!parsed.classes.length) delete parsed.classes; if (!parsed.attributes.length) delete parsed.attributes; if (!parsed.pseudos.length) delete parsed.pseudos; if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null; return Selectors.Cache.parsed[selector] = parsed; }, parseTagAndID: function(selector){ var tag = selector.match(Selectors.RegExps.tag); var id = selector.match(Selectors.RegExps.id); return [(tag) ? tag[1] : '*', (id) ? id[1] : false]; }, filter: function(item, parsed, local){ var i; if (parsed.classes){ for (i = parsed.classes.length; i--; i){ var cn = parsed.classes[i]; if (!Selectors.Filters.byClass(item, cn)) return false; } } if (parsed.attributes){ for (i = parsed.attributes.length; i--; i){ var att = parsed.attributes[i]; if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false; } } if (parsed.pseudos){ for (i = parsed.pseudos.length; i--; i){ var psd = parsed.pseudos[i]; if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false; } } return true; }, getByTagAndID: function(ctx, tag, id){ if (id){ var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true); return (item && Selectors.Filters.byTag(item, tag)) ? [item] : []; } else { return ctx.getElementsByTagName(tag); } }, search: function(self, expression, local){ var splitters = []; var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){ splitters.push(m1); return ':)' + m2; }).split(':)'); var items, filtered, item; for (var i = 0, l = selectors.length; i < l; i++){ var selector = selectors[i]; if (i == 0 && Selectors.RegExps.quick.test(selector)){ items = self.getElementsByTagName(selector); continue; } var splitter = splitters[i - 1]; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (i == 0){ items = Selectors.Utils.getByTagAndID(self, tag, id); } else { var uniques = {}, found = []; for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques); items = found; } var parsed = Selectors.Utils.parseSelector(selector); if (parsed){ filtered = []; for (var m = 0, n = items.length; m < n; m++){ item = items[m]; if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item); } items = filtered; } } return items; } }; Selectors.Getters = { ' ': function(found, self, tag, id, uniques){ var items = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = items.length; i < l; i++){ var item = items[i]; if (Selectors.Utils.chk(item, uniques)) found.push(item); } return found; }, '>': function(found, self, tag, id, uniques){ var children = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = children.length; i < l; i++){ var child = children[i]; if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child); } return found; }, '+': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); break; } } return found; }, '~': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (!Selectors.Utils.chk(self, uniques)) break; if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); } } return found; } }; Selectors.Filters = { byTag: function(self, tag){ return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag)); }, byID: function(self, id){ return (!id || (self.id && self.id == id)); }, byClass: function(self, klass){ return (self.className && self.className.contains && self.className.contains(klass, ' ')); }, byPseudo: function(self, parser, argument, local){ return parser.call(self, argument, local); }, byAttribute: function(self, name, operator, value){ var result = Element.prototype.getProperty.call(self, name); if (!result) return (operator == '!='); if (!operator || value == undefined) return true; switch (operator){ case '=': return (result == value); case '*=': return (result.contains(value)); case '^=': return (result.substr(0, value.length) == value); case '$=': return (result.substr(result.length - value.length) == value); case '!=': return (result != value); case '~=': return result.contains(value, ' '); case '|=': return result.contains(value, '-'); } return false; } }; Selectors.Pseudo = new Hash({ // w3c pseudo selectors checked: function(){ return this.checked; }, empty: function(){ return !(this.innerText || this.textContent || '').length; }, not: function(selector){ return !Element.match(this, selector); }, contains: function(text){ return (this.innerText || this.textContent || '').contains(text); }, 'first-child': function(){ return Selectors.Pseudo.index.call(this, 0); }, 'last-child': function(){ var element = this; while ((element = element.nextSibling)){ if (element.nodeType == 1) return false; } return true; }, 'only-child': function(){ var prev = this; while ((prev = prev.previousSibling)){ if (prev.nodeType == 1) return false; } var next = this; while ((next = next.nextSibling)){ if (next.nodeType == 1) return false; } return true; }, 'nth-child': function(argument, local){ argument = (argument == undefined) ? 'n' : argument; var parsed = Selectors.Utils.parseNthArgument(argument); if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local); var count = 0; local.positions = local.positions || {}; var uid = $uid(this); if (!local.positions[uid]){ var self = this; while ((self = self.previousSibling)){ if (self.nodeType != 1) continue; count ++; var position = local.positions[$uid(self)]; if (position != undefined){ count = position + count; break; } } local.positions[uid] = count; } return (local.positions[uid] % parsed.a == parsed.b); }, // custom pseudo selectors index: function(index){ var element = this, count = 0; while ((element = element.previousSibling)){ if (element.nodeType == 1 && ++count > index) return false; } return (count == index); }, even: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n+1', local); }, odd: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n', local); }, selected: function(){ return this.selected; }, enabled: function(){ return (this.disabled === false); } }); /* --- script: Event.js description: Contains the Event Class, to make the event object cross-browser. license: MIT-style license. requires: - /Window - /Document - /Hash - /Array - /Function - /String provides: [Event] ... */ var Event = new Native({ name: 'Event', initialize: function(event, win){ win = win || window; var doc = win.document; event = event || win.event; if (event.$extended) return event; this.$extended = true; var type = event.type; var target = event.target || event.srcElement; while (target && target.nodeType == 3) target = target.parentNode; if (type.test(/key/)){ var code = event.which || event.keyCode; var key = Event.Keys.keyOf(code); if (type == 'keydown'){ var fKey = code - 111; if (fKey > 0 && fKey < 13) key = 'f' + fKey; } key = key || String.fromCharCode(code).toLowerCase(); } else if (type.match(/(click|mouse|menu)/i)){ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; var page = { x: event.pageX || event.clientX + doc.scrollLeft, y: event.pageY || event.clientY + doc.scrollTop }; var client = { x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY }; if (type.match(/DOMMouseScroll|mousewheel/)){ var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; } var rightClick = (event.which == 3) || (event.button == 2); var related = null; if (type.match(/over|out/)){ switch (type){ case 'mouseover': related = event.relatedTarget || event.fromElement; break; case 'mouseout': related = event.relatedTarget || event.toElement; } if (!(function(){ while (related && related.nodeType == 3) related = related.parentNode; return true; }).create({attempt: Browser.Engine.gecko})()) related = false; } } return $extend(this, { event: event, type: type, page: page, client: client, rightClick: rightClick, wheel: wheel, relatedTarget: related, target: target, code: code, key: key, shift: event.shiftKey, control: event.ctrlKey, alt: event.altKey, meta: event.metaKey }); } }); Event.Keys = new Hash({ 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 }); Event.implement({ stop: function(){ return this.stopPropagation().preventDefault(); }, stopPropagation: function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; }, preventDefault: function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }); /* --- script: Element.Event.js description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events. license: MIT-style license. requires: - /Element - /Event provides: [Element.Event] ... */ Element.Properties.events = {set: function(events){ this.addEvents(events); }}; Native.implement([Element, Window, Document], { addEvent: function(type, fn){ var events = this.retrieve('events', {}); events[type] = events[type] || {'keys': [], 'values': []}; if (events[type].keys.contains(fn)) return this; events[type].keys.push(fn); var realType = type, custom = Element.Events.get(type), condition = fn, self = this; if (custom){ if (custom.onAdd) custom.onAdd.call(this, fn); if (custom.condition){ condition = function(event){ if (custom.condition.call(this, event)) return fn.call(this, event); return true; }; } realType = custom.base || realType; } var defn = function(){ return fn.call(self); }; var nativeEvent = Element.NativeEvents[realType]; if (nativeEvent){ if (nativeEvent == 2){ defn = function(event){ event = new Event(event, self.getWindow()); if (condition.call(self, event) === false) event.stop(); }; } this.addListener(realType, defn); } events[type].values.push(defn); return this; }, removeEvent: function(type, fn){ var events = this.retrieve('events'); if (!events || !events[type]) return this; var pos = events[type].keys.indexOf(fn); if (pos == -1) return this; events[type].keys.splice(pos, 1); var value = events[type].values.splice(pos, 1)[0]; var custom = Element.Events.get(type); if (custom){ if (custom.onRemove) custom.onRemove.call(this, fn); type = custom.base || type; } return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; }, addEvents: function(events){ for (var event in events) this.addEvent(event, events[event]); return this; }, removeEvents: function(events){ var type; if ($type(events) == 'object'){ for (type in events) this.removeEvent(type, events[type]); return this; } var attached = this.retrieve('events'); if (!attached) return this; if (!events){ for (type in attached) this.removeEvents(type); this.eliminate('events'); } else if (attached[events]){ while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]); attached[events] = null; } return this; }, fireEvent: function(type, args, delay){ var events = this.retrieve('events'); if (!events || !events[type]) return this; events[type].keys.each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, cloneEvents: function(from, type){ from = document.id(from); var fevents = from.retrieve('events'); if (!fevents) return this; if (!type){ for (var evType in fevents) this.cloneEvents(from, evType); } else if (fevents[type]){ fevents[type].keys.each(function(fn){ this.addEvent(type, fn); }, this); } return this; } }); Element.NativeEvents = { click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons mousewheel: 2, DOMMouseScroll: 2, //mouse wheel mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement keydown: 2, keypress: 2, keyup: 2, //keyboard focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window error: 1, abort: 1, scroll: 1 //misc }; (function(){ var $check = function(event){ var related = event.relatedTarget; if (related == undefined) return true; if (related === false) return false; return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related)); }; Element.Events = new Hash({ mouseenter: { base: 'mouseover', condition: $check }, mouseleave: { base: 'mouseout', condition: $check }, mousewheel: { base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel' } }); })(); /* --- script: Class.js description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. license: MIT-style license. requires: - /$util - /Native - /Array - /String - /Function - /Number - /Hash provides: [Class] ... */ function Class(params){ if (params instanceof Function) params = {initialize: params}; var newClass = function(){ Object.reset(this); if (newClass._prototyping) return this; this._current = $empty; var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; delete this._current; delete this.caller; return value; }.extend(this); newClass.implement(params); newClass.constructor = Class; newClass.prototype.constructor = newClass; return newClass; }; Function.prototype.protect = function(){ this._protected = true; return this; }; Object.reset = function(object, key){ if (key == null){ for (var p in object) Object.reset(object, p); return object; } delete object[key]; switch ($type(object[key])){ case 'object': var F = function(){}; F.prototype = object[key]; var i = new F; object[key] = Object.reset(i); break; case 'array': object[key] = $unlink(object[key]); break; } return object; }; new Native({name: 'Class', initialize: Class}).extend({ instantiate: function(F){ F._prototyping = true; var proto = new F; delete F._prototyping; return proto; }, wrap: function(self, key, method){ if (method._origin) method = method._origin; return function(){ if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.'); var caller = this.caller, current = this._current; this.caller = current; this._current = arguments.callee; var result = method.apply(this, arguments); this._current = current; this.caller = caller; return result; }.extend({_owner: self, _origin: method, _name: key}); } }); Class.implement({ implement: function(key, value){ if ($type(key) == 'object'){ for (var p in key) this.implement(p, key[p]); return this; } var mutator = Class.Mutators[key]; if (mutator){ value = mutator.call(this, value); if (value == null) return this; } var proto = this.prototype; switch ($type(value)){ case 'function': if (value._hidden) return this; proto[key] = Class.wrap(this, key, value); break; case 'object': var previous = proto[key]; if ($type(previous) == 'object') $mixin(previous, value); else proto[key] = $unlink(value); break; case 'array': proto[key] = $unlink(value); break; default: proto[key] = value; } return this; } }); Class.Mutators = { Extends: function(parent){ this.parent = parent; this.prototype = Class.instantiate(parent); this.implement('parent', function(){ var name = this.caller._name, previous = this.caller._owner.parent.prototype[name]; if (!previous) throw new Error('The method "' + name + '" has no parent.'); return previous.apply(this, arguments); }.protect()); }, Implements: function(items){ $splat(items).each(function(item){ if (item instanceof Function) item = Class.instantiate(item); this.implement(item); }, this); } }; /* --- script: Class.Extras.js description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. license: MIT-style license. requires: - /Class provides: [Chain, Events, Options] ... */ var Chain = new Class({ $chain: [], chain: function(){ this.$chain.extend(Array.flatten(arguments)); return this; }, callChain: function(){ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; }, clearChain: function(){ this.$chain.empty(); return this; } }); var Events = new Class({ $events: {}, addEvent: function(type, fn, internal){ type = Events.removeOn(type); if (fn != $empty){ this.$events[type] = this.$events[type] || []; this.$events[type].include(fn); if (internal) fn.internal = true; } return this; }, addEvents: function(events){ for (var type in events) this.addEvent(type, events[type]); return this; }, fireEvent: function(type, args, delay){ type = Events.removeOn(type); if (!this.$events || !this.$events[type]) return this; this.$events[type].each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, removeEvent: function(type, fn){ type = Events.removeOn(type); if (!this.$events[type]) return this; if (!fn.internal) this.$events[type].erase(fn); return this; }, removeEvents: function(events){ var type; if ($type(events) == 'object'){ for (type in events) this.removeEvent(type, events[type]); return this; } if (events) events = Events.removeOn(events); for (type in this.$events){ if (events && events != type) continue; var fns = this.$events[type]; for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]); } return this; } }); Events.removeOn = function(string){ return string.replace(/^on([A-Z])/, function(full, first){ return first.toLowerCase(); }); }; var Options = new Class({ setOptions: function(){ this.options = $merge.run([this.options].extend(arguments)); if (!this.addEvent) return this; for (var option in this.options){ if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; this.addEvent(option, this.options[option]); delete this.options[option]; } return this; } }); /* --- script: Request.js description: Powerful all purpose Request Class. Uses XMLHTTPRequest. license: MIT-style license. requires: - /Element - /Chain - /Events - /Options - /Browser provides: [Request] ... */ var Request = new Class({ Implements: [Chain, Events, Options], options: {/* onRequest: $empty, onComplete: $empty, onCancel: $empty, onSuccess: $empty, onFailure: $empty, onException: $empty,*/ url: '', data: '', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }, format: false, method: 'post', link: 'ignore', isSuccess: null, emulation: true, urlEncoded: true, encoding: 'utf-8', evalScripts: false, evalResponse: false, noCache: false }, initialize: function(options){ this.xhr = new Browser.Request(); this.setOptions(options); this.options.isSuccess = this.options.isSuccess || this.isSuccess; this.headers = new Hash(this.options.headers); }, onStateChange: function(){ if (this.xhr.readyState != 4 || !this.running) return; this.running = false; this.status = 0; $try(function(){ this.status = this.xhr.status; }.bind(this)); this.xhr.onreadystatechange = $empty; if (this.options.isSuccess.call(this, this.status)){ this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML}; this.success(this.response.text, this.response.xml); } else { this.response = {text: null, xml: null}; this.failure(); } }, isSuccess: function(){ return ((this.status >= 200) && (this.status < 300)); }, processScripts: function(text){ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text); return text.stripScripts(this.options.evalScripts); }, success: function(text, xml){ this.onSuccess(this.processScripts(text), xml); }, onSuccess: function(){ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); }, failure: function(){ this.onFailure(); }, onFailure: function(){ this.fireEvent('complete').fireEvent('failure', this.xhr); }, setHeader: function(name, value){ this.headers.set(name, value); return this; }, getHeader: function(name){ return $try(function(){ return this.xhr.getResponseHeader(name); }.bind(this)); }, check: function(){ if (!this.running) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(this.caller.bind(this, arguments)); return false; } return false; }, send: function(options){ if (!this.check(options)) return this; this.running = true; var type = $type(options); if (type == 'string' || type == 'element') options = {data: options}; var old = this.options; options = $extend({data: old.data, url: old.url, method: old.method}, options); var data = options.data, url = String(options.url), method = options.method.toLowerCase(); switch ($type(data)){ case 'element': data = document.id(data).toQueryString(); break; case 'object': case 'hash': data = Hash.toQueryString(data); } if (this.options.format){ var format = 'format=' + this.options.format; data = (data) ? format + '&' + data : format; } if (this.options.emulation && !['get', 'post'].contains(method)){ var _method = '_method=' + method; data = (data) ? _method + '&' + data : _method; method = 'post'; } if (this.options.urlEncoded && method == 'post'){ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding); } if (this.options.noCache){ var noCache = 'noCache=' + new Date().getTime(); data = (data) ? noCache + '&' + data : noCache; } var trimPosition = url.lastIndexOf('/'); if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); if (data && method == 'get'){ url = url + (url.contains('?') ? '&' : '?') + data; data = null; } this.xhr.open(method.toUpperCase(), url, this.options.async); this.xhr.onreadystatechange = this.onStateChange.bind(this); this.headers.each(function(value, key){ try { this.xhr.setRequestHeader(key, value); } catch (e){ this.fireEvent('exception', [key, value]); } }, this); this.fireEvent('request'); this.xhr.send(data); if (!this.options.async) this.onStateChange(); return this; }, cancel: function(){ if (!this.running) return this; this.running = false; this.xhr.abort(); this.xhr.onreadystatechange = $empty; this.xhr = new Browser.Request(); this.fireEvent('cancel'); return this; } }); (function(){ var methods = {}; ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){ methods[method] = function(){ var params = Array.link(arguments, {url: String.type, data: $defined}); return this.send($extend(params, {method: method})); }; }); Request.implement(methods); })(); Element.Properties.send = { set: function(options){ var send = this.retrieve('send'); if (send) send.cancel(); return this.eliminate('send').store('send:options', $extend({ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') }, options)); }, get: function(options){ if (options || !this.retrieve('send')){ if (options || !this.retrieve('send:options')) this.set('send', options); this.store('send', new Request(this.retrieve('send:options'))); } return this.retrieve('send'); } }; Element.implement({ send: function(url){ var sender = this.get('send'); sender.send({data: this, url: url || sender.options.url}); return this; } }); /* --- script: Request.HTML.js description: Extends the basic Request Class with additional methods for interacting with HTML responses. license: MIT-style license. requires: - /Request - /Element provides: [Request.HTML] ... */ Request.HTML = new Class({ Extends: Request, options: { update: false, append: false, evalScripts: true, filter: false }, processHTML: function(text){ var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i); text = (match) ? match[1] : text; var container = new Element('div'); return $try(function(){ var root = '<root>' + text + '</root>', doc; if (Browser.Engine.trident){ doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = false; doc.loadXML(root); } else { doc = new DOMParser().parseFromString(root, 'text/xml'); } root = doc.getElementsByTagName('root')[0]; if (!root) return null; for (var i = 0, k = root.childNodes.length; i < k; i++){ var child = Element.clone(root.childNodes[i], true, true); if (child) container.grab(child); } return container; }) || container.set('html', text); }, success: function(text){ var options = this.options, response = this.response; response.html = text.stripScripts(function(script){ response.javascript = script; }); var temp = this.processHTML(response.html); response.tree = temp.childNodes; response.elements = temp.getElements('*'); if (options.filter) response.tree = response.elements.filter(options.filter); if (options.update) document.id(options.update).empty().set('html', response.html); else if (options.append) document.id(options.append).adopt(temp.getChildren()); if (options.evalScripts) $exec(response.javascript); this.onSuccess(response.tree, response.elements, response.html, response.javascript); } }); Element.Properties.load = { set: function(options){ var load = this.retrieve('load'); if (load) load.cancel(); return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options)); }, get: function(options){ if (options || ! this.retrieve('load')){ if (options || !this.retrieve('load:options')) this.set('load', options); this.store('load', new Request.HTML(this.retrieve('load:options'))); } return this.retrieve('load'); } }; Element.implement({ load: function(){ this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type})); return this; } }); /* --- script: Fx.js description: Contains the basic animation logic to be extended by all other Fx Classes. license: MIT-style license. requires: - /Chain - /Events - /Options provides: [Fx] ... */ var Fx = new Class({ Implements: [Chain, Events, Options], options: { /* onStart: $empty, onCancel: $empty, onComplete: $empty, */ fps: 50, unit: false, duration: 500, link: 'ignore' }, initialize: function(options){ this.subject = this.subject || this; this.setOptions(options); this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt(); var wait = this.options.wait; if (wait === false) this.options.link = 'cancel'; }, getTransition: function(){ return function(p){ return -(Math.cos(Math.PI * p) - 1) / 2; }; }, step: function(){ var time = $time(); if (time < this.time + this.options.duration){ var delta = this.transition((time - this.time) / this.options.duration); this.set(this.compute(this.from, this.to, delta)); } else { this.set(this.compute(this.from, this.to, 1)); this.complete(); } }, set: function(now){ return now; }, compute: function(from, to, delta){ return Fx.compute(from, to, delta); }, check: function(){ if (!this.timer) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(this.caller.bind(this, arguments)); return false; } return false; }, start: function(from, to){ if (!this.check(from, to)) return this; this.from = from; this.to = to; this.time = 0; this.transition = this.getTransition(); this.startTimer(); this.onStart(); return this; }, complete: function(){ if (this.stopTimer()) this.onComplete(); return this; }, cancel: function(){ if (this.stopTimer()) this.onCancel(); return this; }, onStart: function(){ this.fireEvent('start', this.subject); }, onComplete: function(){ this.fireEvent('complete', this.subject); if (!this.callChain()) this.fireEvent('chainComplete', this.subject); }, onCancel: function(){ this.fireEvent('cancel', this.subject).clearChain(); }, pause: function(){ this.stopTimer(); return this; }, resume: function(){ this.startTimer(); return this; }, stopTimer: function(){ if (!this.timer) return false; this.time = $time() - this.time; this.timer = $clear(this.timer); return true; }, startTimer: function(){ if (this.timer) return false; this.time = $time() - this.time; this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); return true; } }); Fx.compute = function(from, to, delta){ return (to - from) * delta + from; }; Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; /* --- script: Fx.CSS.js description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. license: MIT-style license. requires: - /Fx - /Element.Style provides: [Fx.CSS] ... */ Fx.CSS = new Class({ Extends: Fx, //prepares the base from/to object prepare: function(element, property, values){ values = $splat(values); var values1 = values[1]; if (!$chk(values1)){ values[1] = values[0]; values[0] = element.getStyle(property); } var parsed = values.map(this.parse); return {from: parsed[0], to: parsed[1]}; }, //parses a value into an array parse: function(value){ value = $lambda(value)(); value = (typeof value == 'string') ? value.split(' ') : $splat(value); return value.map(function(val){ val = String(val); var found = false; Fx.CSS.Parsers.each(function(parser, key){ if (found) return; var parsed = parser.parse(val); if ($chk(parsed)) found = {value: parsed, parser: parser}; }); found = found || {value: val, parser: Fx.CSS.Parsers.String}; return found; }); }, //computes by a from and to prepared objects, using their parsers. compute: function(from, to, delta){ var computed = []; (Math.min(from.length, to.length)).times(function(i){ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); }); computed.$family = {name: 'fx:css:value'}; return computed; }, //serves the value as settable serve: function(value, unit){ if ($type(value) != 'fx:css:value') value = this.parse(value); var returned = []; value.each(function(bit){ returned = returned.concat(bit.parser.serve(bit.value, unit)); }); return returned; }, //renders the change to an element render: function(element, property, value, unit){ element.setStyle(property, this.serve(value, unit)); }, //searches inside the page css to find the values for a selector search: function(selector){ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; var to = {}; Array.each(document.styleSheets, function(sheet, j){ var href = sheet.href; if (href && href.contains('://') && !href.contains(document.domain)) return; var rules = sheet.rules || sheet.cssRules; Array.each(rules, function(rule, i){ if (!rule.style) return; var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ return m.toLowerCase(); }) : null; if (!selectorText || !selectorText.test('^' + selector + '$')) return; Element.Styles.each(function(value, style){ if (!rule.style[style] || Element.ShortStyles[style]) return; value = String(rule.style[style]); to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value; }); }); }); return Fx.CSS.Cache[selector] = to; } }); Fx.CSS.Cache = {}; Fx.CSS.Parsers = new Hash({ Color: { parse: function(value){ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; }, compute: function(from, to, delta){ return from.map(function(value, i){ return Math.round(Fx.compute(from[i], to[i], delta)); }); }, serve: function(value){ return value.map(Number); } }, Number: { parse: parseFloat, compute: Fx.compute, serve: function(value, unit){ return (unit) ? value + unit : value; } }, String: { parse: $lambda(false), compute: $arguments(1), serve: $arguments(0) } }); /* --- script: Fx.Tween.js description: Formerly Fx.Style, effect to transition any CSS property for an element. license: MIT-style license. requires: - /Fx.CSS provides: [Fx.Tween, Element.fade, Element.highlight] ... */ Fx.Tween = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = document.id(element); this.parent(options); }, set: function(property, now){ if (arguments.length == 1){ now = property; property = this.property || this.options.property; } this.render(this.element, property, now, this.options.unit); return this; }, start: function(property, from, to){ if (!this.check(property, from, to)) return this; var args = Array.flatten(arguments); this.property = this.options.property || args.shift(); var parsed = this.prepare(this.element, this.property, args); return this.parent(parsed.from, parsed.to); } }); Element.Properties.tween = { set: function(options){ var tween = this.retrieve('tween'); if (tween) tween.cancel(); return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('tween')){ if (options || !this.retrieve('tween:options')) this.set('tween', options); this.store('tween', new Fx.Tween(this, this.retrieve('tween:options'))); } return this.retrieve('tween'); } }; Element.implement({ tween: function(property, from, to){ this.get('tween').start(arguments); return this; }, fade: function(how){ var fade = this.get('tween'), o = 'opacity', toggle; how = $pick(how, 'toggle'); switch (how){ case 'in': fade.start(o, 1); break; case 'out': fade.start(o, 0); break; case 'show': fade.set(o, 1); break; case 'hide': fade.set(o, 0); break; case 'toggle': var flag = this.retrieve('fade:flag', this.get('opacity') == 1); fade.start(o, (flag) ? 0 : 1); this.store('fade:flag', !flag); toggle = true; break; default: fade.start(o, arguments); } if (!toggle) this.eliminate('fade:flag'); return this; }, highlight: function(start, end){ if (!end){ end = this.retrieve('highlight:original', this.getStyle('background-color')); end = (end == 'transparent') ? '#fff' : end; } var tween = this.get('tween'); tween.start('background-color', start || '#ffff88', end).chain(function(){ this.setStyle('background-color', this.retrieve('highlight:original')); tween.callChain(); }.bind(this)); return this; } }); /* --- script: Fx.Transitions.js description: Contains a set of advanced transitions to be used with any of the Fx Classes. license: MIT-style license. credits: - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools. requires: - /Fx provides: [Fx.Transitions] ... */ Fx.implement({ getTransition: function(){ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; if (typeof trans == 'string'){ var data = trans.split(':'); trans = Fx.Transitions; trans = trans[data[0]] || trans[data[0].capitalize()]; if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; } return trans; } }); Fx.Transition = function(transition, params){ params = $splat(params); return $extend(transition, { easeIn: function(pos){ return transition(pos, params); }, easeOut: function(pos){ return 1 - transition(1 - pos, params); }, easeInOut: function(pos){ return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2; } }); }; Fx.Transitions = new Hash({ linear: $arguments(0) }); Fx.Transitions.extend = function(transitions){ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); }; Fx.Transitions.extend({ Pow: function(p, x){ return Math.pow(p, x[0] || 6); }, Expo: function(p){ return Math.pow(2, 8 * (p - 1)); }, Circ: function(p){ return 1 - Math.sin(Math.acos(p)); }, Sine: function(p){ return 1 - Math.sin((1 - p) * Math.PI / 2); }, Back: function(p, x){ x = x[0] || 1.618; return Math.pow(p, 2) * ((x + 1) * p - x); }, Bounce: function(p){ var value; for (var a = 0, b = 1; 1; a += b, b /= 2){ if (p >= (7 - 4 * a) / 11){ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); break; } } return value; }, Elastic: function(p, x){ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); } }); ['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ Fx.Transitions[transition] = new Fx.Transition(function(p){ return Math.pow(p, [i + 2]); }); }); /* --- script: Fx.Morph.js description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. license: MIT-style license. requires: - /Fx.CSS provides: [Fx.Morph] ... */ Fx.Morph = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = document.id(element); this.parent(options); }, set: function(now){ if (typeof now == 'string') now = this.search(now); for (var p in now) this.render(this.element, p, now[p], this.options.unit); return this; }, compute: function(from, to, delta){ var now = {}; for (var p in from) now[p] = this.parent(from[p], to[p], delta); return now; }, start: function(properties){ if (!this.check(properties)) return this; if (typeof properties == 'string') properties = this.search(properties); var from = {}, to = {}; for (var p in properties){ var parsed = this.prepare(this.element, p, properties[p]); from[p] = parsed.from; to[p] = parsed.to; } return this.parent(from, to); } }); Element.Properties.morph = { set: function(options){ var morph = this.retrieve('morph'); if (morph) morph.cancel(); return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('morph')){ if (options || !this.retrieve('morph:options')) this.set('morph', options); this.store('morph', new Fx.Morph(this, this.retrieve('morph:options'))); } return this.retrieve('morph'); } }; Element.implement({ morph: function(props){ this.get('morph').start(props); return this; } }); /* --- script: DomReady.js description: Contains the custom event domready. license: MIT-style license. requires: - /Element.Event provides: [DomReady] ... */ Element.Events.domready = { onAdd: function(fn){ if (Browser.loaded) fn.call(this); } }; (function(){ var domready = function(){ if (Browser.loaded) return; Browser.loaded = true; window.fireEvent('domready'); document.fireEvent('domready'); }; window.addEvent('load', domready); if (Browser.Engine.trident){ var temp = document.createElement('div'); (function(){ ($try(function(){ temp.doScroll(); // Technique by Diego Perini return document.id(temp).inject(document.body).set('html', 'temp').dispose(); })) ? domready() : arguments.callee.delay(50); })(); } else if (Browser.Engine.webkit && Browser.Engine.version < 525){ (function(){ (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50); })(); } else { document.addEvent('DOMContentLoaded', domready); } })(); /* --- script: Cookie.js description: Class for creating, reading, and deleting browser Cookies. license: MIT-style license. credits: - Based on the functions by Peter-Paul Koch (http://quirksmode.org). requires: - /Options provides: [Cookie] ... */ var Cookie = new Class({ Implements: Options, options: { path: false, domain: false, duration: false, secure: false, document: document }, initialize: function(key, options){ this.key = key; this.setOptions(options); }, write: function(value){ value = encodeURIComponent(value); if (this.options.domain) value += '; domain=' + this.options.domain; if (this.options.path) value += '; path=' + this.options.path; if (this.options.duration){ var date = new Date(); date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); value += '; expires=' + date.toGMTString(); } if (this.options.secure) value += '; secure'; this.options.document.cookie = this.key + '=' + value; return this; }, read: function(){ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); return (value) ? decodeURIComponent(value[1]) : null; }, dispose: function(){ new Cookie(this.key, $merge(this.options, {duration: -1})).write(''); return this; } }); Cookie.write = function(key, value, options){ return new Cookie(key, options).write(value); }; Cookie.read = function(key){ return new Cookie(key).read(); }; Cookie.dispose = function(key, options){ return new Cookie(key, options).dispose(); }; /* --- script: Swiff.js description: Wrapper for embedding SWF movies. Supports External Interface Communication. license: MIT-style license. credits: - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. requires: - /Options - /$util provides: [Swiff] ... */ var Swiff = new Class({ Implements: [Options], options: { id: null, height: 1, width: 1, container: null, properties: {}, params: { quality: 'high', allowScriptAccess: 'always', wMode: 'transparent', swLiveConnect: true }, callBacks: {}, vars: {} }, toElement: function(){ return this.object; }, initialize: function(path, options){ this.instance = 'Swiff_' + $time(); this.setOptions(options); options = this.options; var id = this.id = options.id || this.instance; var container = document.id(options.container); Swiff.CallBacks[this.instance] = {}; var params = options.params, vars = options.vars, callBacks = options.callBacks; var properties = $extend({height: options.height, width: options.width}, options.properties); var self = this; for (var callBack in callBacks){ Swiff.CallBacks[this.instance][callBack] = (function(option){ return function(){ return option.apply(self.object, arguments); }; })(callBacks[callBack]); vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; } params.flashVars = Hash.toQueryString(vars); if (Browser.Engine.trident){ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; params.movie = path; } else { properties.type = 'application/x-shockwave-flash'; properties.data = path; } var build = '<object id="' + id + '"'; for (var property in properties) build += ' ' + property + '="' + properties[property] + '"'; build += '>'; for (var param in params){ if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />'; } build += '</object>'; this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; }, replaces: function(element){ element = document.id(element, true); element.parentNode.replaceChild(this.toElement(), element); return this; }, inject: function(element){ document.id(element, true).appendChild(this.toElement()); return this; }, remote: function(){ return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments)); } }); Swiff.CallBacks = {}; Swiff.remote = function(obj, fn){ var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>'); return eval(rs); }; // end Greased MooTools // hack to circumvent 'bug' when overriding toString (and others): // https://mootools.lighthouseapp.com/projects/2706/tickets/651-classtostring-broken-on-122-big-regression ['toString', 'toLocaleString', 'valueOf', 'toSource', 'watch', 'unwatch', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable'].each(function (method) { Class.Mutators[method] = $arguments(0); }); if (Browser.Engine.webkit || // Chrome, Safari Browser.Engine.presto) { // Opera var keyPrefix = 'UNIFIEDChallengesCheckPlayTool.'; GM_getLoggedInUser = function () { try { return $('TopBar').getElement('table.Header').getElement('td.Status').getElement('a.Pale').get('html'); } catch (e) { try { return $('TopBar').getElement('table.Header').getElement('td.Status').getElement('a.ywa-track').get('html'); } catch (e) { GM_log("unable to retrieve user (" + e + ")"); } } } GM_getGlobalNsid = function () { var reMatch = /global_nsid[ =]+\'([^\']+)\'/; var retval; $$('script[type=text/javascript]').each( function (script) { if ($chk(retval)) { return; } var html = script.get('html'); if (html.match(reMatch)) { try { retval = html.match(reMatch)[1]; } catch (e) { GM_log("error executing RegExp: " + e); retval = undefined; } } }); return retval; } GM_log = function (message) { if (Browser.Engine.webkit) { console.info(message); } else { opera.postError(message); } } GM_getValue = function(key, defValue) { var retval = window.localStorage.getItem(keyPrefix + key); if (retval == null) { return defValue; } return retval; } GM_setValue = function(key, value) { try { window.localStorage.setItem(keyPrefix + key, value); } catch (e) { GM_log("error setting value: " + e); } } GM_deleteValue = function(key) { try { window.localStorage.removeItem(keyPrefix + key); } catch (e) { GM_log("error removing value: " + e); } } GM_listValues = function() { var list = []; var reKey = new RegExp("^" + keyPrefix); for (var i = 0, il = window.localStorage.length; i < il; i++) { // only use the script's own keys var key = window.localStorage.key(i); if (key.match(reKey)) { list.push(key.replace(keyPrefix, '')); } } return list; } GM_getObject = function (key) { var value = GM_getValue(key); if ($chk(value)) { return JSON.parse(value); } return null; } GM_storeObject = function (key, value) { GM_setValue(key, JSON.stringify(value)); } GM_getMagisterLudi = function () { // the following api_key is reserved for this application // if you need an api_key for your own application, please request one at // http://www.flickr.com/services/apps/create/apply/ // if you request a Non-Commercial key, you'll get it instantly return 'a78ba83c374022595dc9073986735dcb'; // the app's own key } GM_getAuthHash = function () { var reMatch = /global_auth_hash[ =]+\'([^\']+)\'/; var retval; $$('script[type=text/javascript]').each( function (script) { if ($chk(retval)) { return; } var html = script.get('html'); if (html.match(reMatch)) { try { retval = html.match(reMatch)[1]; } catch (e) { GM_log("error executing RegExp: " + e); retval = undefined; } } }); return retval; } GM_getAuthToken = function () { var reMatch = /global_auth_token[ =]+\'([^\']+)\'/; var retval; $$('script[type=text/javascript]').each( function (script) { if ($chk(retval)) { return; } var html = script.get('html'); if (html.match(reMatch)) { try { retval = html.match(reMatch)[1]; } catch (e) { GM_log("error executing RegExp: " + e); retval = undefined; } } }); return retval; } } else { GM_getLoggedInUser = function () { return unsafeWindow.global_name; } GM_getGlobalNsid = function () { return unsafeWindow.global_nsid; } GM_getObject = function (key) { var value = GM_getValue(key); if ($chk(value)) { return eval('(' + value + ')'); } return null; } GM_storeObject = function (key, value) { GM_setValue(key, uneval(value)); } GM_getMagisterLudi = function () { // the following api_key is reserved for this application // if you need an api_key for your own application, please request one at // http://www.flickr.com/services/apps/create/apply/ // if you request a Non-Commercial key, you'll get it instantly return 'a78ba83c374022595dc9073986735dcb'; // the app's own key } GM_getAuthHash = function () { return unsafeWindow.global_auth_hash; } GM_getAuthToken = function () { return unsafeWindow.global_auth_token; } } var updatingIcon = 'http://l.yimg.com/www.flickr.com/images/pulser2.gif'; var errorIcon = 'http://l.yimg.com/g/images/icon_error_x_small.png'; var defaultCheckIconSmall = 'http://l.yimg.com/g/images/icon_check_small.png'; function showUpdateNotification() { var color = 'white'; var bgColor = 'black'; var updatespan = new Element('span', { // copied from Google++ userscript: styles: { padding: '2px 4px', background: bgColor + ' none repeat scroll 0% 0%', display: 'block', '-moz-background-clip': 'border', '-moz-background-origin': 'padding', '-moz-background-inline-policy': 'continuous', position: 'fixed', opacity: '0.7', 'z-index': 100, bottom: '5px', right: '5px' } }).inject($(document).getElement("body")); new Element('a', { html: 'UNIFIED Challenges CheckPlay Tool: update available', href: (Browser.Engine.webkit || Browser.Engine.presto) ? 'http://userscripts.org/scripts/show/' + scriptNumber: // Chrome users: first uninstall the old version 'http://userscripts.org/scripts/source/' + scriptNumber + '.user.js', target: (Browser.Engine.webkit || Browser.Engine.presto) ? '_blank' : '', title: (Browser.Engine.webkit || Browser.Engine.presto) ? 'to the script\'s install page (opens in new tab)' : 'click to install new version', styles: { 'color': color, 'text-decoration': 'none' }, events: { click: function () { if (!(Browser.Engine.webkit || Browser.Engine.presto)) { // Firefox: install directly this.innerHTML = "↓ ↓ wait to reload the page until Greasemonkey finishes installing ↓ ↓"; this.setStyle('color', "orange"); GM_deleteValue("onlineVersion"); } } } }).inject(updatespan); new Element('a', { html: ' (Changes)', title: 'opens in new tab', href: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623882744032/', styles: { 'text-decoration': 'none' }, target: '_blank' }).inject(updatespan); } function storeVersion() { // only called on iframe containing script's meta data var onlineVersion = $$('body')[0].getElement('pre').get('html').split(/@version\s* /)[1].split(/[\r\n]+/)[0]; GM_setValue('onlineVersion', onlineVersion); // works only in FF } function checkVersion() { var lastVersionCheckTime = GM_getValue("lastVersionCheckTime"); var elapsedtime; var CPStartTime = new Date(); if ($chk(lastVersionCheckTime)) { elapsedtime = CPStartTime.getTime() - lastVersionCheckTime; } if (!$chk(lastVersionCheckTime) || elapsedtime / 1000 > 60 * 60 * 12) { //more then 12h ago new Element('iframe', { src: "http://userscripts.org/scripts/source/" + scriptNumber + ".meta.js", styles: { width: 0, height: 0, display: 'none', visibility: 'hidden' } }).inject($$('body')[0]); // the script also run within this iframe => storeVersion is called GM_setValue("lastVersionCheckTime", CPStartTime.getTime().toString()); } var onlineVersion = GM_getValue("onlineVersion"); if ($chk(onlineVersion)) { var updateAvailable = false; var reVersionMatch = /(\d+)\.(\d+)\.(\d+)/; var onlineVersionParts = reVersionMatch.exec(onlineVersion); var currentVersionParts = reVersionMatch.exec(CPtoolversion); var onlineVersionMajor, onlineVersionMinor, onlineVersionBuild; //[ onlineVersion, onlineVersionMajor, onlineVersionMinor, onlineVersionBuild ] = onlineVersionParts; 'invalid left-hand side' in Chrome onlineVersionMajor = onlineVersionParts[1]; onlineVersionMinor = onlineVersionParts[2]; onlineVersionBuild = onlineVersionParts[3]; var currentVersionMajor, currentVersionMinor, currentVersionBuild; //[ currentVersion, currentVersionMajor, currentVersionMinor, currentVersionBuild] = currentVersionParts; currentVersionMajor = currentVersionParts[1]; currentVersionMinor = currentVersionParts[2]; currentVersionBuild = currentVersionParts[3]; // first check major: important update! => rewrite, flickr updates, greasemonkey updates if (parseInt(onlineVersionMajor, 10) > parseInt(currentVersionMajor, 10)) { updateAvailable = true; } else if (parseInt(onlineVersionMajor, 10) === parseInt(currentVersionMajor, 10)) { // we don't want to downgrade // minor version update => new functionality if (parseInt(onlineVersionMinor, 10) > parseInt(currentVersionMinor, 10)) { updateAvailable = true; } else if (parseInt(onlineVersionMinor, 10) === parseInt(currentVersionMinor, 10)) { // we don't want to downgrade // build version update => bugfixes if (parseInt(onlineVersionBuild, 10) > parseInt(currentVersionBuild, 10)) { updateAvailable = true; } } } if (updateAvailable) { showUpdateNotification(); } } } // ----------- var CPStartTime = new Date(); // former commonLibraryNG: var UCPGroupConfigReader = new Class({ timeBetweenReads: 7 * 24 * 60 * 60 * 1000, // a week groupListingURL: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623452533109/', initialize: function () { }, checkForUpdates: function (groupname, force, callback) { var groupListCounter = GM_getValue("UCP.groupConfig.readCounter.list"); if (!$chk(groupListCounter)) { groupListCounter = 0; } if (groupListCounter > 0) { GM_setValue("UCP.groupConfig.readCounter.list", groupListCounter - 1); } else { this.readGrouplistURL(false, callback); } if ($chk(GM_getValue("UCP.groupConfig." + groupname))) { var lastReadTime = GM_getValue("UCP.groupConfig.lastReadTime." + groupname); var now = new Date().getTime(); var elapsedTime = $chk(lastReadTime) ? now - lastReadTime : this.timeBetweenReads + 1; if (elapsedTime > this.timeBetweenReads || force) { GM_log("updating " + groupname + " definitions"); this.readGroupConfigURL(groupname, force === true ? true : false, callback); } } }, createGroupConfig: function (groupname) { if (!$chk(groupname)) { var reGroupnameMatch = /.*flickr.com\/groups\/([^\/.]*)\//; groupname = reGroupnameMatch.exec(document.location.href)[1]; } //GM_log("reading config for '" + groupname + "'"); var storedConfig; if ($chk(GM_getValue("UCP.groupConfig." + groupname))) { storedConfig = GM_getValue("UCP.groupConfig." + groupname); } if ($chk(storedConfig) && storedConfig.match('groupId')) { // test on groupId for versions prior to CL v0.0.7 try { return new UCPGroupConfig(GM_getObject("UCP.groupConfig." + groupname)); } catch (e) { // parse error? GM_deleteValue("UCP.groupConfig." + groupname); return null; } } else { // if not available, read from URL, synchronously //GM_log("reading configURL for '" + groupname + "' synchronously"); // make sure the group list is read also: a new group should not have to hit F5 twice, just to get support! GM_deleteValue("UCP.groupConfig.list"); this.readGroupConfigURL(groupname, true); storedConfig = GM_getValue("UCP.groupConfig." + groupname); if ($chk(storedConfig)) { try { return new UCPGroupConfig(GM_getObject("UCP.groupConfig." + groupname)); } catch (e) { // parse error? GM_deleteValue("UCP.groupConfig." + groupname); return null; } } GM_log("did not find definitions for " + groupname); return null; } }, readGroupConfigURL: function (groupname, synchronous, callback) { //GM_log("reading group url for '" + groupname + "' - synchronous: " + synchronous); if (synchronous) { this.readGrouplistURL(true, callback); } var groupList = this.groupList(); if (!$chk(groupList[groupname]) || !$chk(groupList[groupname].definitions)) { return; } var groupUrl = groupList[groupname].definitions; //GM_log("reading group definitions for '" + groupname + "' (" + groupList[groupname].groupId + ") from '" + groupUrl + "'"); var request = new Request({ url: groupUrl, async: !synchronous, onSuccess: function (responseText, responseXML) { var discussionHTML = responseText; var tempDiv = new Element('div', { html: discussionHTML.stripScripts() }); var announcement = tempDiv.getElement('td.Said p'); announcement.getElements('small').each(function (small) { small.dispose(); }); var groupConfiguration = announcement.textContent .trim() .replace(""", "\"") .replace(/\n/g, '') ; // Flickr changes //GM_log("groupConfiguration: " + groupConfiguration); var onlineConfig; try { onlineConfig = JSON.parse(groupConfiguration); } catch (e) { GM_log("json error: " + e); // JSON can't handle 'reName': /^d.../ // Chrome only uses JSON!! try { onlineConfig = eval('(' + groupConfiguration + ')'); } catch (e) { GM_log("error evaluating onlineConfig: " + e); GM_log("onlineConfig: " + groupConfiguration); if ($chk(callback)) { callback( { stat: 'error', 'error': 'error evaluating onlineConfig: ' + e } ); } return; } } //GM_log("preparing default states"); // reset defaults for non-defined states // TODO: document var defaultStates = { open: "OPEN", // no photos yet waitingForEntries: "OPEN", // at least one photo; some groups use "ON HOLD", .. vote: /VOTE/i, // some groups use "VOTING", .. closed: "CLOSED", // expired: "EXPIRED", voided: "VOIDED" }; // inject group list items for (var item in groupList[groupname]) { if (groupList[groupname].hasOwnProperty(item)) { onlineConfig[item] = groupList[groupname][item]; } } if (!$chk(onlineConfig.states)) { onlineConfig.states = defaultStates; // special case: vote is defined as a regexp in the defaults => Chrome won't have a vote state! onlineConfig.states.voteRegExp = { expression: 'VOTE', flags: 'i' }; } else { // warning: special case: vote is defined as a regexp in the defaults! for (var state in defaultStates) { if (defaultStates.hasOwnProperty(state)) { if (!$chk(onlineConfig.states[state])) { if (state === 'vote' && !$chk(onlineConfig.states['voteRegExp'])) { onlineConfig.states.voteRegExp = { expression: 'VOTE', flags: 'i' }; } else { onlineConfig.states[state] = defaultStates[state]; } } } } } GM_storeObject("UCP.groupConfig." + groupname, onlineConfig); // add a random factor to minimize concurrent read from UCP and UCPA // ony update after xx runs of checkForUpdates() GM_setValue("UCP.groupConfig.lastReadTime." + groupname, new Date().getTime().toString()); if ($chk(callback)) { callback( { stat: 'ok' } ); } }.bind(this) }).get(); }, readGrouplistURL: function (synchronous, callback) { //GM_log("reading group list"); var request = new Request({ method: 'get', url: this.groupListingURL, async: !synchronous, onSuccess: function (responseText, responseXML) { var discussionHTML = responseText; var tempDiv = new Element('div', { html: discussionHTML.stripScripts() }); var announcement = tempDiv.getElement('td.Said p'); announcement.getElements('small').each(function (small) { small.dispose(); }); var groupList = announcement.textContent.trim().replace(/\n/g, ''); var groups = {}; try { groups = JSON.parse(groupList); } catch(e) { GM_log("json error: " + e); try { groups = eval('(' + groupList + ')'); } catch (e) { GM_log("error parsing groupList result: " + e); GM_log("groupList: " + groupList); if ($chk(callback)) { callback( { stat: 'error', error: 'error parsing groupList result: ' + e }); } return; } } // inject groupname $each(groups, function (value, key) { value.groupname = key; }); //ucpStoredGrouplist = groups; //GM_log("storing group list"); GM_storeObject("UCP.groupConfig.list", groups); // update again after xx this.checkForUpdates() runs GM_setValue("UCP.groupConfig.readCounter.list", 37 + $random(0, 11)); if ($chk(callback)) { callback( { stat: 'ok' } ); } }.bind(this) }).send(); }, groupList: function () { if ($chk(GM_getValue("UCP.groupConfig.list"))) { //GM_log("working with storage value, and update in background"); try { return GM_getObject("UCP.groupConfig.list"); } catch (e) { // parse error? GM_deleteValue("UCP.groupConfig.list"); //return null; continue: reread } } //GM_log("reading list url"); this.readGrouplistURL(true); //GM_log("returning stored dict: '" + ucpStoredGrouplist + "'"); try { return GM_getObject("UCP.groupConfig.list"); } catch (e) { // parse error? GM_deleteValue("UCP.groupConfig.list"); return null; } } }); var UCPGroupConfig = new Class({ Implements: [Options], options: { challengeDefinitions: {}, groupLimit: -1, states: {}, allowsPhotoInAnnouncement: false, nonPhotoImages: {}, groupLimitLabelAddon: "", skipFirstReply: false, // FavesContest shows last winning score in first reply => Finished skipFirstTwoRepliesForVotes: false, // FavesContest have a second reply '... 25-50 faves...', recognized as a vote mandatoryGroupLabels: false, automaticVoteStart: true, languageOverrides: {}, legacyLabels: null, groupname: null, // the part of the Flickr URL name: null, // the human name groupId: null }, initialize: function (options) { this.setOptions(options); // override states with RegExp's [ 'open', 'waitingForEntries', 'closed', 'vote', 'expired', 'voided' ].each(function (state) { // override states with RegExp's if ($chk(options.states[state + 'RegExp'])) { //GM_log("creating " + state + " regexp"); this.options.states[state] = new RegExp(options.states[state + 'RegExp'].expression, options.states[state + 'RegExp'].flags); } if (!$chk(this.options.states[state])) { GM_log("!!! no definition for state '" + state + "' !!!"); } }, this); }, // accessors groupname: function () { return this.options.groupname; }, challengeDefinitions: function () { return this.options.challengeDefinitions; }, groupLimit: function () { return this.options.groupLimit; }, groupLimitLabelAddon: function () { return this.options.groupLimitLabelAddon; }, states: function () { return this.options.states; }, legacyLabels: function () { return this.options.legacyLabels; }, skipFirstReply: function () { return this.options.skipFirstReply; }, skipFirstTwoRepliesForVotes: function () { return this.options.skipFirstTwoRepliesForVotes; }, hasLegacyLabels: function () { return $chk(this.options.legacyLabels); }, mandatoryGroupLabels: function () { return this.options.mandatoryGroupLabels; }, automaticVoteStart: function () { return this.options.automaticVoteStart; }, languageOverrides: function () { return this.options.languageOverrides; }, allowsPhotoInAnnouncement: function () { return this.options.allowsPhotoInAnnouncement; }, nonPhotoImages: function () { return this.options.nonPhotoImages; }, groupId: function () { return this.options.groupId; }, reExcludeMatch: function () { if ($chk(this.reExcludeMatchRegExp)) { return this.reExcludeMatchRegExp; } if (!$chk(this.options.excludes) || !$chk(this.options.excludes.matches) || !$chk(this.options.excludes.indexes)) { return /[^.]/; // match nothing } if ($chk(this.options.excludes.matches.reMatchRegExp)) { // only one this.reExcludeMatchRegExp = new RegExp(this.options.excludes.matches.reMatchRegExp.expression, this.options.excludes.matches.flags); return this.reExcludeMatchRegExp; } var reMatches = []; for (var name in this.options.excludes.matches) { if (this.options.excludes.matches.hasOwnProperty(name)) { reMatches.push(this.options.excludes.matches[name]); } } // GM_log("creating regexp with '" + reMatches.join("|") + "'"); return new RegExp(reMatches.join("|"), "ig"); }, excludeReplyIndexes: function () { if (!$chk(this.options.excludes) || !$chk(this.options.excludes.matches) || !$chk(this.options.excludes.indexes)) { return []; } var retval = []; for (var index in this.options.excludes.indexes) { if (this.options.excludes.indexes.hasOwnProperty(index)) { retval.push(this.options.excludes.indexes[index]); } } //GM_log("debug: " + retval); return retval; }, checkPlayerEntries: function (args) { if (this.groupLimit() <= 0) { return; } var statusDiv; var statusParagraph; // TODO: for challenge groups where there is no group limit, but there is a limit for some specific challenges, // use the phrase "You have reached the maximum play limit in the capped competitions" (Fotocompetitions group) if (args.entries === this.groupLimit()) { statusDiv = $(args.statusDivId); statusDiv.style.display = 'block'; statusParagraph = $(args.statusParagraphId); statusParagraph.set('html', "UCheckPlayNG: You entered " + args.entries + " challenges and have reached your maximum play limit! " + ($chk(this.groupLimitLabelAddon()) ? this.groupLimitLabelAddon() : "")); statusParagraph.style.color = 'red'; } if (args.entries > this.groupLimit()) { statusDiv = $(args.statusDivId); statusDiv.style.display = 'block'; statusParagraph = $(args.statusParagraphId); statusParagraph.set('html', "UCheckPlayNG: You entered over " + this.groupLimit() + " challenges and are thus breaking the rules! Please remove your latest entry! " + ($chk(this.groupLimitLabelAddon()) ? this.groupLimitLabelAddon() : "")); statusParagraph.style.color = 'red'; statusParagraph.style.textDecorationUnderline = 'underline'; statusParagraph.style.fontWeight = 'bold'; } }, extractChallengeDefinition: function (challengeName) { // do not run .each()! must stop after the first match: priorities if (!$chk(this.challengeDefinitions())) { GM_log("no challenge definitions for this group"); return new UCPChallengeDefinition({ scoreType: "UNKNOWN" }); } for (var name in this.challengeDefinitions()) { //GM_log("name: " + name); if (this.challengeDefinitions().hasOwnProperty(name)) { var challengeDef = this.challengeDefinitions()[name]; // TMOACG still uses reName (and others) var nameIndication = challengeDef.reName; // this is a simple JSON object if ($chk(challengeDef.reNameRegExp)) { if (!$chk(challengeDef.reNameRegExpObject)) { challengeDef.reNameRegExpObject = new RegExp(challengeDef.reNameRegExp.expression, challengeDef.reNameRegExp.flags); } nameIndication = challengeDef.reNameRegExpObject; } if (!$chk(nameIndication)) { GM_log("!! missing reName, or reNameRegExp !!"); } else { var theMatch = challengeName.match(nameIndication); // DEBUG //GM_log("comparing "+challengeName+" with "+nameIndication+": "+theMatch); if ($chk(theMatch)) { // inject name challengeDef.name = name; return new UCPChallengeDefinition(challengeDef); } } } } // none found! GM_log(challengeName + " not found in definitions"); return new UCPChallengeDefinition({ scoreType: "UNKNOWN" }); }, skipChallenge: function (ucpThread) { if (ucpThread.challengeName().match(this.states().closed)) { // GM_log("challengeName: " + ucpThread.challengeName() + " matches closed state: " + this.states().closed); return true; } if (ucpThread.challengeName().match(this.states().expired)) { return true; } if (ucpThread.challengeName().match(this.states().voided)) { return true; } for (var name in this.challengeDefinitions()) { if (this.challengeDefinitions().hasOwnProperty(name)) { var challengeDef = this.challengeDefinitions()[name]; var specialName = challengeDef.reName; if (ucpThread.challengeName().match(specialName)) { return false; } } } if (ucpThread.challengeName().match(this.states().open)) { return false; } if (ucpThread.challengeName().match(this.states().waitingForEntries)) { return false; } if (ucpThread.challengeName().match(this.states().vote)) { return false; } return true; }, isGroupAdministratorOrModerator: function (userNsid) { var retval = GM_getValue("UCP.groupAdminOrMod." + this.groupname() + "." + userNsid) === true || GM_getValue("UCP.groupAdminOrMod." + this.groupname() + "." + userNsid) === 'true'; var lastReadTime = GM_getValue("UCP.groupAdminOrMod." + this.groupname() + "." + userNsid + ".lastReadTime"); //GM_log("admin: " + retval + " - time: " + lastReadTime); var asyncUpdate = true; if (!$chk(lastReadTime)) { //GM_log("using API to retrieve admin info"); asyncUpdate = false; retval = false; } else { // run this test only once a day var now = new Date().getTime(); var elapsedTime = now - lastReadTime; if (elapsedTime < 24 * 60 * 60 * 1000) { return retval; } } var groupname = this.groupname(); var magisterLudi = GM_getMagisterLudi(); var apiData = { api_key: magisterLudi, auth_hash: GM_getAuthHash(), auth_token: GM_getAuthToken(), format: 'json', nojsoncallback: 1, method: 'flickr.groups.members.getList', group_id: this.groupId(), membertypes: '3,4', per_page: 500 // one page should be sufficient? }; new Request({ url: "http://api.flickr.com/", async: asyncUpdate, onFailure: function (response) { retval = false; GM_log("failed reading members from group"); }, onSuccess: function (responseText, responseXML) { var retval = false; var result; try { result = JSON.parse(responseText); } catch (e) { result = eval('(' + responseText + ')'); } if (result.stat === 'fail') { GM_log("failed reading members from group: " +result.code + " - " + result.message); retval = false; return; } var members = result.members; $each(members.member, function (member) { if (member.nsid === userNsid) { retval = true; } }); GM_setValue("UCP.groupAdminOrMod." + groupname + "." + userNsid, retval); GM_setValue("UCP.groupAdminOrMod." + groupname + "." + userNsid + ".lastReadTime", new Date().getTime().toString()); } }).get("/services/rest", apiData); // debug: me if (userNsid === '37989307@N08') { return true; } return retval; } }); var ucpDialogStyle = { background: '#BFBFBF', '-moz-border-radius': '1em', '-webkit-border-radius': '1em', '-khtml-border-radius': '1em', 'border-radius': '1em', border: 'grey solid 1px' }; var ucpLanguages = []; // TODO: remove playerMayVote, playerFinished, playerMustVote, playerVoted titles from languages var UCPLanguageConfigReader = new Class({ timeBetweenReads: 30 * 24 * 60 * 60 * 1000, // a month languageListingURL: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623513185619/', initialize: function () { }, checkForUpdates: function (languagename, force, callback) { if ($chk(GM_getValue("UCP.languageConfig." + languagename))) { //GM_log("found " + languagename + " in storage, updating in background"); // update in the background // make sure the language list is read also: a user should not have to hit F5 twice, just to get support! var lastReadTime = GM_getValue("UCP.languageConfig.lastReadTime." + languagename); var now = new Date().getTime(); var elapsedTime = $chk(lastReadTime) ? now - lastReadTime : this.timeBetweenReads + 1; if (elapsedTime > this.timeBetweenReads || force) { GM_log("updating '" + languagename + "' definitions"); this.readLanguageConfigURL(languagename, force === true ? true : false, callback); } } }, createLanguage: function (languagename, legacyLabels, languageOverrides) { if (!$chk(languagename)) { languagename = 'English'; } if ($chk(ucpLanguages[languagename])) { return ucpLanguages[languagename]; } //GM_log("reading config for '" + languagename + "'"); var storedConfig; if ($chk(GM_getValue("UCP.languageConfig." + languagename))) { storedConfig = GM_getValue("UCP.languageConfig." + languagename); } if (!$chk(storedConfig)) { //GM_log(languagename + " not found in storage, updating"); this.readLanguageConfigURL(languagename, true); storedConfig = GM_getValue("UCP.languageConfig." + languagename); } // use the stored config, and read updates, in the background var result; try { result = JSON.parse(storedConfig); } catch (e) { result = eval('(' + storedConfig + ')'); } if ($chk(result)) { //GM_log("storing language " + languagename); GM_storeObject("UCP.languageConfig." + languagename, result); // inject languagename result.language = languagename; var retval = new UCPLanguage(result); if ($chk(languageOverrides)) { retval.useLanguageOverrides(languageOverrides); } if ($chk(legacyLabels)) { retval.useLegacyLabels(legacyLabels); } ucpLanguages[languagename] = retval; return retval; } else { GM_log("result is invalid (storedConfig: " + storedConfig + ")"); GM_deleteValue("UCP.languageConfig." + languagename); // retry with English return this.createLanguage('English', legacyLabels, languageOverrides); } return null; }, readLanguageConfigURL: function (languagename, synchronous, callback) { //GM_log("reading language url for '" + languagename + "'"); try { var languageList = GM_getObject("UCP.languageConfig.list"); } catch (e) { // parse error GM_deleteValue("UCP.languageConfig.list"); } if (!$chk(languageList)) { this.readLanguagelistURL(true, callback); languageList = GM_getObject("UCP.languageConfig.list"); } var languageUrl = languageList[languagename].definitions; //GM_log("reading language definitions for '" + languagename + "': '" + languageUrl + "'"); var request = new Request({ method: 'get', url: languageUrl, async: !synchronous, onSuccess: function (responseText, responseXML) { var discussionHTML = responseText; var tempDiv = new Element('div', { html: discussionHTML.stripScripts() }); var announcement = tempDiv.getElement('td.Said p'); announcement.getElements('small').each(function (small) { small.dispose(); }); var languageConfiguration = announcement.textContent .trim() .replace(""", "\"") .replace(/\n/g, ''); //GM_log("languageConfiguration: " + languageConfiguration); var onlineConfig; try { onlineConfig = JSON.parse(languageConfiguration); } catch (e) { GM_log("json error: " + e); try { onlineConfig = eval ('(' + languageConfiguration + ')'); } catch (e) { GM_log("error reading languageConfiguration: " + e); GM_log("language configuration: " + languageConfiguration); if ($chk(callback)) { callback( { stat: 'error', error: 'error reading languageConfiguration: ' + e } ); } return; } } if (languagename !== 'English') { // reset defaults for non-defined parts, or languages // TODO: document var defaultLanguage = this.createLanguage('English'); this.checkForUpdates('English'); if (!$chk(onlineConfig.titles)) { onlineConfig.titles = defaultLanguage.titles(); } else { for (var title in defaultLanguage.titles()) { if (defaultLanguage.titles().hasOwnProperty(title)) { if (!$chk(onlineConfig.titles[title])) { //GM_log("title '" + title + "' not found, replacing with default"); onlineConfig.titles[title] = defaultLanguage.titles()[title]; } } } } if (!$chk(onlineConfig.labels)) { onlineConfig.labels = defaultLanguage.labels(); } else { for (var label in defaultLanguage.labels()) { if (defaultLanguage.labels().hasOwnProperty(label)) { if (!$chk(onlineConfig.labels[label])) { //GM_log("label '" + label + "' not found, replacing with default (" + defaultLanguage.labels()[label] + ")"); onlineConfig.labels[label] = defaultLanguage.labels()[label]; } } } } } GM_storeObject("UCP.languageConfig." + languagename, onlineConfig); // only update after xx runs of checkForUpdates() GM_setValue("UCP.languageConfig.lastReadTime." + languagename, new Date().getTime().toString()); if ($chk(callback)) { callback( { stat: 'ok' } ); } }.bind(this) }).send(); }, readLanguagelistURL: function (synchronous, callback) { //GM_log("reading language list"); var request = new Request({ method: 'get', url: this.languageListingURL, async: !synchronous, onSuccess: function (responseText, responseXML) { var discussionHTML = responseText; var tempDiv = new Element('div', { html: discussionHTML.stripScripts() }); var announcement = tempDiv.getElement('td.Said p'); announcement.getElements('small').each(function (small) { small.dispose(); }); var languageList = announcement.textContent; this.languages = {}; //GM_log("languageList: " + languageList); try { this.languages = JSON.parse(languageList); } catch (e) { try { this.languages = eval('(' + languageList + ')'); } catch (e) { GM_log("error parsing languageList result: " + e); GM_log("languageList: " + languageList); if ($chk(callback)) { callback( { stat: 'error', error: 'error parsing languagelist result: ' + e } ); } return; } } GM_storeObject("UCP.languageConfig.list", this.languages); if ($chk(callback)) { callback( { stat: 'ok' } ); } }.bind(this) }).send(); }, getLanguageList: function () { try { var list = GM_getObject("UCP.languageConfig.list"); } catch (e) { // parse error GM_deleteValue("UCP.languageConfig.list"); } if (list === undefined) { this.readLanguagelistURL(true); list = GM_getObject("UCP.languageConfig.list"); } return list; } }); var UCPLanguage = new Class({ Implements: Options, options: { name: undefined, titles: undefined, labels: undefined }, initialize: function (options) { this.setOptions(options); }, name: function () { return this.options.name; }, titles: function () { return this.options.titles; }, labels: function () { return this.options.labels; }, useLegacyLabels: function (legacyLabels) { for (var label in legacyLabels) { if (legacyLabels.hasOwnProperty(label)) { this.options.labels[label] = legacyLabels[label]; } } }, useLanguageOverrides: function (languageOverrides) { if ($chk(languageOverrides)) { if ($chk(languageOverrides.titleOverrides)) { var titleOverrides = languageOverrides.titleOverrides; for (var titleOverride in titleOverrides) { if (titleOverrides.hasOwnProperty(titleOverride)) { this.options.titles[titleOverride] = this.options.titles[titleOverrides[titleOverride]]; } } } if ($chk(languageOverrides.labelOverrides)) { var labelOverrides = languageOverrides.labelOverrides; for (var labelOverride in labelOverrides) { if (labelOverrides.hasOwnProperty(labelOverride)) { this.options.labels[labelOverride] = this.options.labels[labelOverrides[labelOverride]]; } } } } } }); function createTopicListingStatusDecorator (chlgstatus, ucpGroupPreferences, ucpLanguage) { if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage(ucpGroupPreferences.language(), ucpGroupPreferences.useLegacyLabels() && ucpGroupPreferences.groupConfig().hasLegacyLabels() ? ucpGroupPreferences.groupConfig().legacyLabels() : undefined, ucpGroupPreferences.groupConfig().languageOverrides()); } if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage('English', ucpGroupPreferences.useLegacyLabels() && ucpGroupPreferences.groupConfig().hasLegacyLabels() ? ucpGroupPreferences.groupConfig().legacyLabels() : undefined, ucpGroupPreferences.groupConfig().languageOverrides()); } switch (chlgstatus) { case "Filled": return { name: chlgstatus, // TODO: use scoreTypeLabelOverride from challengeDefinition labels: [ ucpLanguage.labels().filled, ucpGroupPreferences.groupConfig().automaticVoteStart() ? ucpLanguage.labels().vote : "" ], title: ucpLanguage.titles().filled + (ucpGroupPreferences.groupConfig().automaticVoteStart() ? ucpLanguage.titles().automaticVoteStart : ""), color: ucpLanguage.labels().filledColor, addWarning: true }; case "Finished": return { name: chlgstatus, labels: [ ucpLanguage.labels().finished ], title: ucpLanguage.titles().finished, color: ucpLanguage.labels().finishedColor, addWarning: true }; case "--VOTE--": // fall through case "VOTE": return { name: chlgstatus, labels: [ ucpLanguage.labels().vote ], title: ucpLanguage.titles().vote, color: ucpLanguage.labels().voteColor, addWarning: true }; case "Voted": return { name: chlgstatus, labels: [ ucpLanguage.labels().voted ], title: ucpLanguage.titles().voted, color: ucpLanguage.labels().votedColor, addWarning: true }; case "Voided": case "Closed": return { name: chlgstatus, labels: [ ucpLanguage.labels().closed ], title: ucpLanguage.titles().closed, color: ucpLanguage.labels().ignoreColor }; case "Open": return { name: chlgstatus, labels: [ ucpLanguage.labels().open ], title: ucpLanguage.titles().open, color: ucpLanguage.labels().openColor, addWarning: true }; case "OK": return { name: chlgstatus, labels: [ ucpLanguage.labels().waitingForEntries ], title: ucpLanguage.titles().open, color: ucpLanguage.labels().waitingForEntriesColor, addWarning: true }; case "Excluded": return { name: chlgstatus, labels: [ ucpLanguage.labels().excluded ], title: ucpLanguage.titles().excluded, color: ucpLanguage.labels().excludedColor, addWarning: true }; case "ErrExclPlay": return { name: chlgstatus, labels: [ ucpLanguage.labels().errExcl ], title: ucpLanguage.titles().errExclPlay, color: ucpLanguage.labels().errExclColor, addWarning: true }; case "UPDATING": return { name: chlgstatus, labels: [ ucpLanguage.labels().updating ], title: ucpLanguage.titles().updating, color: "" }; case "ERRORLOADING": return { name: chlgstatus, labels: [ ucpLanguage.labels().errorLoading ], title: ucpLanguage.titles().errorLoading, color: ucpLanguage.labels().errorLoadingColor, addWarning: true }; case "ERRORPARSING": return { name: chlgstatus, labels: [ ucpLanguage.labels().errorParsing ], title: ucpLanguage.titles().errorParsing, color: ucpLanguage.labels().errorParsingColor, addWarning: true }; case "Player": return { name: chlgstatus, labels: [ ucpLanguage.labels().player ], title: ucpLanguage.titles().player, color: ucpLanguage.labels().playerColor, addWarning: true }; case "PlayerVoted": return { name: chlgstatus, labels: [ ucpLanguage.labels().player, ucpLanguage.labels().voted ], title: ucpLanguage.titles().player + " " + ucpLanguage.titles().voted, color: ucpLanguage.labels().votedColor, addWarning: true }; case "PlayerMustVote": return { name: chlgstatus, labels: [ ucpLanguage.labels().player, ucpLanguage.labels().vote ], title: ucpLanguage.titles().player + " " + ucpLanguage.titles().vote, color: ucpLanguage.labels().voteColor, addWarning: true }; case "PlayerMayVote": return { name: chlgstatus, labels: [ ucpLanguage.labels().player ], title: ucpLanguage.titles().player, color: ucpLanguage.labels().playerColor, addWarning: true }; case "PlayerFilled": return { name: chlgstatus, labels: [ ucpLanguage.labels().player, ucpLanguage.labels().filled ], title: ucpLanguage.titles().player + " " + ucpLanguage.titles().filled + (ucpGroupPreferences.groupConfig().automaticVoteStart() ? ucpLanguage.titles().automaticVoteStart : ""), color: ucpLanguage.labels().filledColor, addWarning: true }; case "PlayerFinished": return { name: chlgstatus, labels: [ ucpLanguage.labels().player, ucpLanguage.labels().finished ], title: ucpLanguage.titles().player + " " + ucpLanguage.titles().finished, color: ucpLanguage.labels().finishedColor, addWarning: true }; case "UNKNOWN": case "Unknown": case "---": return { name: chlgstatus, labels: [ ucpLanguage.labels().ignore ], title: ucpLanguage.titles().ignore, color: "" }; case "none": return { name: chlgstatus, labels: [ ucpLanguage.labels().open + "?" ], title: ucpLanguage.titles().open, color: ucpLanguage.labels().openColor, addWarning: true }; default: GM_log("no decorator for chlgstatus '" + chlgstatus + "'"); return { name: "error" }; } } function createChallengePageStatusDecorator (chlgstatus, ucpGroupPreferences, ucpLanguage) { if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage(ucpGroupPreferences.language(), ucpGroupPreferences.useLegacyLabels() && ucpGroupPreferences.groupConfig().hasLegacyLabels() ? ucpGroupPreferences.groupConfig().legacyLabels() : undefined, ucpGroupPreferences.groupConfig().languageOverrides()); } if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage('English', ucpGroupPreferences.useLegacyLabels() && ucpGroupPreferences.groupConfig().hasLegacyLabels() ? ucpGroupPreferences.groupConfig().legacyLabels() : undefined, ucpGroupPreferences.groupConfig().languageOverrides()); } switch (chlgstatus) { case "Filled": return { name: chlgstatus, title: ucpLanguage.titles().filled + (ucpGroupPreferences.groupConfig().automaticVoteStart() ? ucpLanguage.titles().automaticVoteStart : "") }; case "Finished": return { name: chlgstatus, title: ucpLanguage.titles().finished }; case "--VOTE--": // fall through case "VOTE": return { name: chlgstatus, title: ucpLanguage.titles().vote }; case "Voted": return { name: chlgstatus, title: ucpLanguage.titles().voted }; case "Voided": case "Closed": return { name: chlgstatus, title: ucpLanguage.titles().closed }; case "Open": return { name: chlgstatus, title: ucpLanguage.titles().open }; case "OK": return { name: chlgstatus, title: ucpLanguage.titles().open }; case "Excluded": return { name: chlgstatus, title: ucpLanguage.titles() }; case "ErrExclPlay": return { name: chlgstatus, title: ucpLanguage.titles().errExclPlay }; case "UPDATING": return { name: chlgstatus, title: ucpLanguage.titles().updating }; case "ERRORLOADING": return { name: chlgstatus, title: ucpLanguage.titles().errorLoading }; case "ERRORPARSING": return { name: chlgstatus, title: ucpLanguage.titles().errorParsing }; case "Player": return { name: chlgstatus, title: ucpLanguage.titles().player }; case "PlayerVoted": return { name: chlgstatus, title: ucpLanguage.titles().player + " " + ucpLanguage.titles().voted }; case "PlayerMustVote": return { name: chlgstatus, title: ucpLanguage.titles().player + " " + ucpLanguage.titles().vote }; case "PlayerMayVote": return { name: chlgstatus, title: ucpLanguage.titles().player }; case "PlayerFilled": return { name: chlgstatus, title: ucpLanguage.titles().player + " " + ucpLanguage.titles().filled + (ucpGroupPreferences.groupConfig().automaticVoteStart() ? ucpLanguage.titles().automaticVoteStart : "") }; case "PlayerFinished": return { name: chlgstatus, title: ucpLanguage.titles().player + " " + ucpLanguage.titles().finished }; case "---": return { name: chlgstatus, title: ucpLanguage.titles().ignore }; case "none": return { name: chlgstatus, title: ucpLanguage.titles().open }; default: GM_log("don't know what to do with status '" + chlgstatus + "'"); return { name: "error" }; } } var UCPChallengeDefinition = new Class({ Implements: [Options], options: { name: undefined, reName: /.*/, neededPhotos: -1, neededScore: -1, scoreType: undefined, countsToLimit: false, limitPerPlayer: -1, playerVoting: 'mayvote', scoresAdded: true, iconChallenge: false, scoreTypeLabelOverride: undefined }, initialize: function (options) { this.setOptions(options); }, name: function () { return this.options.name; }, reName: function () { return this.options.reName; }, neededPhotos: function () { return this.options.neededPhotos; }, setNeededPhotos: function (n) { this.options.neededPhotos = n; }, neededScore: function () { return this.options.neededScore; }, setNeededScore: function (n) { this.options.neededScore = n; }, scoreType: function () { return this.options.scoreType; }, setScoreType: function (s) { this.options.scoreType = s; }, scoreTypeLabelOverride: function () { return this.options.scoreTypeLabelOverride; }, setScoreTypeLabelOverride: function (s) { this.options.scoreTypeLabelOverride = s; }, countsToLimit: function () { return this.options.countsToLimit; }, setCountsToLimit: function (b) { this.options.countsToLimit = b; }, limitPerPlayer: function () { return this.options.limitPerPlayer; }, setLimitPerPlayer: function (n) { this.options.limitPerPlayer = n; }, playerVoting: function () { return this.options.playerVoting; }, setPlayerVoting: function (p) { this.options.playerVoting = p; }, scoresAdded: function () { return this.options.scoresAdded; }, setScoresAdded: function (s) { this.options.scoresAdded = s; }, iconChallenge: function () { return this.options.iconChallenge; }, setIconChallenge: function (b) { this.options.iconChallenge = b; }, toString: function () { return [ 'reName: ' + this.reName(), 'neededPhotos: ' + this.neededPhotos(), 'neededScore: ' + this.neededScore(), 'scoreType: ' + this.scoreType() ].join(","); }, clone: function () { return new UCPChallengeDefinition(this.options); }, nonChallengeType: function () { switch (this.scoreType()) { case "CHAT": case "SHOWROOM": case "GAME": case "MEETANDGREET": case "INFO": case "UNKNOWN": return true; default: return false; } return true; }, readChallengeDefinitionOverrides: function (announcementNode) { var photos, votes, voteType, maxPhotos, playerVoting, countsToLimit, scoresAdded, iconChallenge; var overrides = announcementNode.getElements('img[alt*=UCPoverride]'); var overridesDefined = false; if ($chk(overrides) && overrides.length > 0) { overrides.each(function (overrideImg) { var override = overrideImg.alt; var photosOverrideMatch = override.match(/:photos:(-{0,1}\d+)$/); if (photosOverrideMatch) { var neededPhotosOverride = parseInt(photosOverrideMatch[1], 10); if (this.neededPhotos() !== neededPhotosOverride) { overridesDefined = true; this.setNeededPhotos(neededPhotosOverride); if (this.neededPhotos() < 0) { this.setNeededPhotos(65535); } } } var votesOverrideMatch = override.match(/:votes:(-{0,1}\d+)$/); if (votesOverrideMatch) { var neededScoreOverride = parseInt(votesOverrideMatch[1], 10); if (this.neededScore() !== neededScoreOverride) { overridesDefined = true; this.setNeededScore(neededScoreOverride); if (this.neededScore() < 0) { this.setNeededScore(65535); } } } var scoreTypeOverrideMatch = override.match(/:type:(.*)$/); if (scoreTypeOverrideMatch) { var scoreTypeOverride = scoreTypeOverrideMatch[1]; if (this.scoreType() !== scoreTypeOverride) { overridesDefined = true; this.setScoreType(scoreTypeOverride); } } var scoreTypeLabelOverrideMatch = override.match(/:scoretypelabel:(\w+)$/); if (scoreTypeLabelOverrideMatch) { var scoreTypeLabelOverride = scoreTypeLabelOverrideMatch[1]; if (this.scoreTypeLabelOverride() !== scoreTypeLabelOverride) { overridesDefined = true; this.setScoreTypeLabelOverride(scoreTypeLabelOverride); } } var maxPhotosOverrideMatch = override.match(/:max:(-{0,1}\d+)/); if (maxPhotosOverrideMatch) { var limitPerPlayerOverride = parseInt(maxPhotosOverrideMatch[1], 10); if (this.limitPerPlayer() !== limitPerPlayerOverride) { overridesDefined = true; this.setLimitPerPlayer(limitPerPlayerOverride); } } var playerVotingOverrideMatch = override.match(/:voting:(n\a|mustvote|mayvote|maynotvote)$/); if (playerVotingOverrideMatch) { var playerVotingOverride = playerVotingOverrideMatch[1]; if (this.playerVoting() !== playerVotingOverride) { overridesDefined = true; this.setPlayerVoting(playerVotingOverride); } } var countsToLimitOverrideMatch = override.match(/:grouplimit:(true|false)$/); if (countsToLimitOverrideMatch) { var countsToLimitOverride = (countsToLimitOverrideMatch[1] === "true"); if (this.countsToLimit() !== countsToLimitOverride) { overridesDefined = true; this.setCountsToLimit(countsToLimitOverride); } } var scoresAddedOverrideMatch = override.match(/:added:(true|false)$/); if (scoresAddedOverrideMatch) { var scoresAddedOverride = (scoresAddedOverrideMatch[1] === "true"); if (this.scoresAdded() !== scoresAddedOverride) { overridesDefined = true; this.setScoresAdded(scoresAddedOverride); } } var iconChallengeOverrideMatch = override.match(/:icons:(true|false)$/); if (iconChallengeOverrideMatch) { var iconChallengeOverride = (iconChallengeOverrideMatch[1] === "true"); if (this.iconChallenge() !== iconChallengeOverride) { overridesDefined = true; this.setIconChallenge(iconChallengeOverride); } } }, this); } return overridesDefined; } }); var ucpInsertionIdx = 0; function ucpCreateChallengeThread(options) { var groupConfig = options.groupConfig; var chlgname = options.chlgname; var challengeDefinition = groupConfig.extractChallengeDefinition(chlgname); options.challengeDefinition = challengeDefinition; switch (challengeDefinition.scoreType()) { case "CHAT": return new UCPChatThread(options); case "SHOWROOM": return new UCPShowroomThread(options); case "GAME": return new UCPGameThread(options); case "MEETANDGREET": return new UCPMeetAndGreetThread(options); case "INFO": return new UCPInformationThread(options); case "UNKNOWN": return new UCPUnknownThread(options); default: return new UCPChallengeThread(options); } } var UCPThread = new Class({ Implements: [Options], options: { groupConfig: undefined, chlgname: undefined, chlgAnchor: undefined, labelElement: undefined, feedbackElement: undefined, scoreAnchor: undefined, chlgstatus: "UPDATING", challengeDefinition: undefined, replies: 0, votingErrors: [], validVotingAsStored: true, excludedPlayers: [], url: undefined, topic: undefined, scoreSummary: undefined, lastLoadTime: undefined, needsStatusOnTopicListing: false, needsStatusOnChallengePage: false }, initialize: function (options) { this.setOptions(options); var reTopicMatch = /.*flickr.com\/groups\/[^\/.]*\/discuss\/([0-9]+)/; if (this.options.topic === undefined) { this.options.topic = reTopicMatch.exec(this.options.url)[1]; } }, toString: function () { return [ "url: " + this.url(), "group: " + this.groupname(), "topic: " + this.topic(), "chlgname: " + this.challengeName(), "scroreSummary:" + this.scoreSummary(), "labelElement: " + this.labelElement(), "chlgstatus: " + this.challengeStatus(), "validVoting: " + this.validVoting(), "votingErrors: " + this.votingError(), "lastLoadTime: " + this.lastLoadTime()].join('\n'); }, // accessors groupConfig: function () { return this.options.groupConfig; }, challengeName: function () { return this.options.chlgname; }, setChallengeName: function (name) { this.options.chlgname = name; }, challengeAnchor: function () { return this.options.chlgAnchor; }, labelElement: function () { return this.options.labelElement; }, setLabelElement: function (element) { this.options.labelElement = element; }, scoreAnchor: function () { return this.options.scoreAnchor; }, setScoreAnchor: function (element) { this.options.scoreAnchor = element; }, feedbackElement: function (element) { return this.options.feedbackElement; }, setFeedbackElement: function (element) { this.options.feedbackElement = element; }, challengeStatus: function () { return this.options.chlgstatus; }, setChallengeStatus: function (newstatus) { this.options.chlgstatus = newstatus; }, challengeDefinition: function () { return this.options.challengeDefinition; }, replies: function () { return this.options.replies; }, setReplies: function (replies) { this.options.replies = replies; }, validVoting: function () { return (this.options.votingErrors.length === 0); }, validVotingAsStored: function () { return this.options.validVotingAsStored; }, votingError: function () { return this.options.votingErrors.join(" | "); }, addVotingError: function (error) { if ($chk(error)) { this.options.votingErrors.include(error); } }, addExcludedPlayer: function (username) { if ($chk(username)) { this.options.excludedPlayers.include(username); } }, excludedPlayers: function () { return this.options.excludedPlayers; }, isExcluded: function (username) { if ($chk(username)) { return this.options.excludedPlayers.contains(username); } return false; }, findExcludesInDOMNode: function (node) { reExcludeMatch = this.groupConfig().reExcludeMatch(); var excludedMatch = reExcludeMatch.exec(node.innerHTML); if ($chk(excludedMatch)) { for (var exclMatchIdx = 1, exclMatchLen = excludedMatch.length; exclMatchIdx < exclMatchLen; ++exclMatchIdx) { var excludedPlayer = excludedMatch[exclMatchIdx]; if (excludedPlayer) { // special cases: admin, admins, administrators, I, me: ignore this.addExcludedPlayer(excludedPlayer.replace(/&/g, '&').replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim()); } } } }, url: function () { return this.options.url; }, topic: function () { return this.options.topic; }, scoreSummary: function () { return this.options.scoreSummary; }, setScoreSummary: function (summ) { this.options.scoreSummary = summ; }, lastLoadTime: function () { return this.options.lastLoadTime; }, setLastLoadTime: function (time) { this.options.lastLoadTime = time; }, store: function () { GM_storeObject("UCP." + this.groupname() + "." + this.topic(), { chlgname: this.challengeName(), chlgstatus: this.challengeStatus(), scoreSummary: (this.scoreSummary() ? this.scoreSummary() : ""), replies: this.replies(), votingError: !this.validVoting(), lastLoadTime: (this.lastLoadTime() ? this.lastLoadTime() : 0) }); }, retrieve: function () { var value = GM_getObject("UCP." + this.groupname() + "." + this.topic()); if (value) { this.options.chlgname = value.chlgname; this.setChallengeStatus(value.chlgstatus); this.setScoreSummary(value.scoreSummary); this.setReplies(parseInt(value.replies, 10)); this.options.validVotingAsStored = !value.votingError; this.setLastLoadTime(parseInt(value.lastLoadTime, 10)); } }, groupname: function () { return this.groupConfig().groupname(); }, updateStatus: function (value) { //GM_log("current status: " + this.options.chlgstatus + ", value: " + value); if (this.challengeStatus() === "UPDATING") { this.setChallengeStatus("none"); } if (value === "closed") { // GM_log("updating status to closed"); this.setChallengeStatus("Closed"); return; } if (value === "voided") { this.setChallengeStatus("Voided"); return; } if ((this.challengeStatus() === "none") && (value === "open")) { this.setChallengeStatus("Open"); return; } // set for photoposter (first for loop) if (value === "Excluded") { this.setChallengeStatus("Excluded"); return; } if ((this.challengeStatus() === "none") && (value === "photoposter")) { this.setChallengeStatus("Player"); return; } if ((this.challengeStatus() === "Excluded") && (value === "photoposter")) { this.setChallengeStatus("ErrExclPlay"); return; } if ((this.challengeStatus() === "none") && (value === "filled")) { this.setChallengeStatus("Filled"); return; } // set for voter (second for loop) if ((this.challengeStatus() === "none") && (value === "voter")) { this.setChallengeStatus("Voted"); return; } if ((this.challengeStatus() === "Excluded") && (value === "voter")) { this.setChallengeStatus("Voted"); return; } if ((this.challengeStatus() === "ErrExclPlay") && (value === "voter")) { this.setChallengeStatus("ErrExclPlay"); return; } if ((this.challengeStatus() === "Player") && (value === "voter")) { this.setChallengeStatus("PlayerVoted"); return; } if ((this.challengeStatus() === "Voted") && (value === "voter")) { this.setChallengeStatus("Voted"); //catch a comment and a vote from same player return; } if ((this.challengeStatus() === "Voted") && (value === "photoposter")) { this.setChallengeStatus("PlayerVoted"); return; } if ((this.challengeStatus() === "Player") && (value === "mayvote")) { this.setChallengeStatus("PlayerMayVote"); return; } if ((this.challengeStatus() === "Player") && (value === "mustvote")) { this.setChallengeStatus("PlayerMustVote"); return; } if (this.challengeStatus().match(/^Player/) && (value === "voter")) { this.setChallengeStatus("PlayerVoted"); return; } if (this.challengeStatus().match(/^Player/) && (value === "finished")) { this.setChallengeStatus("PlayerFinished"); return; } if (this.challengeStatus().match("Finished") && (value === "photoposter")) { this.setChallengeStatus("PlayerFinished"); return; } if (this.challengeStatus() === "Player") { this.setChallengeStatus("Player"); // don't overwrite in case of 'maynotvote' return; } if (value === "finished") { this.setChallengeStatus("Finished"); return; } if ((this.challengeStatus() === "none") && (value === "Unknown")) { this.setChallengeStatus("Unknown"); return; } // this.setChallengeStatus(""); keep the status as it is! => [Finished] => RE-VOTE => voter => error! }, printStatus: function (ucpGroupPreferences, ucpLanguage, newchlgstatus) { if (!newchlgstatus || newchlgstatus.length === 0) { newchlgstatus = this.challengeStatus(); } var statusData; if (this.options.needsStatusOnTopicListing) { statusData = createTopicListingStatusDecorator(newchlgstatus, ucpGroupPreferences, ucpLanguage); } else if (this.options.needsStatusOnChallengePage) { statusData = createChallengePageStatusDecorator(newchlgstatus, ucpGroupPreferences, ucpLanguage); } else { return; } this.decorateThread(statusData); // TODO: part of UCPListing.printStatus this.groupConfig().checkPlayerEntries({ entries: playernumber, statusDivId: "UCheckPlayNGStatusDiv", statusParagraphId: "UCheckPlayNGStatus" }); }, decorateThread: function (statusData) { if (this.options.needsStatusOnTopicListing) { if ($chk(this.scoreAnchor()) && $chk(this.scoreSummary()) && (statusData.name === "Finished" || statusData.name === "Voted" || statusData.name === "PlayerVoted" || statusData.name === "PlayerFinished" || statusData.name === "Closed")) { this.scoreAnchor().title = "UCheckPlay score summary: " + this.scoreSummary(); } if (this.labelElement()) { this.labelElement().empty(); this.labelElement().style.textDecoration = 'none'; var myColor = statusData.color; var span; statusData.labels.each(function (label) { this.labelElement().adopt(span = new Element('span', { html: label, styles: { color: myColor }, title: statusData.title })); /* if (label.match(/^<img/)) { GM_log("using image " + label); span.innerHTML = label; } */ }, this); if (statusData.addWarning && !this.validVoting()) { span.set('html', span.get('html') + '(!)'); span.set('title', span.get('title') + ' (!! ' + this.votingError() + ')'); } } if (this.challengeAnchor()) { this.challengeAnchor().set('title', this.options.title); } } else if (this.options.needsStatusOnChallengePage) { var showSummary = $chk(this.scoreAnchor()) && $chk(this.scoreSummary()) && (statusData.name === "Finished" || statusData.name === "Voted" || statusData.name === "PlayerVoted" || statusData.name === "PlayerFinished" || statusData.name === "Closed"); if (showSummary) { this.scoreAnchor().title = "UCheckPlayNG score summary: " + this.scoreSummary(); } if (this.feedbackElement()) { this.feedbackElement().empty(); if (statusData.title) { this.feedbackElement().set('html', 'UCheckPlayNG: ' + statusData.title + '<br/>'); } if (showSummary) { new Element('small', { html: "UCheckPlayNG: " + this.scoreSummary() + "<br/>" }).inject(this.feedbackElement(), 'after'); } } } }, printExcludes: function (challengeAnnouncement) { if (!this.options.needsStatusOnChallengePage) { return; } try { if ($chk(this.options.excludedPlayers) && this.options.excludedPlayers.length > 0) { var pageAnchor = challengeAnnouncement.getElements('small').getLast(); if ($chk(pageAnchor) && $chk(pageAnchor.getParent('div.ucpdiv'))) { pageAnchor = pageAnchor.getParent('div.ucpdiv'); } if (!$chk(pageAnchor)) { pageAnchor = challengeAnnouncement; } this.excludedPlayers().each(function (excludedPlayer) { new Element('small', { html: "UCheckPlayNG: found exclude for <b>" + excludedPlayer + "</b><br/>" } ).inject(pageAnchor, 'after'); }); } } catch (e) { GM_log("error: " + e); } }, sort: function (groupPreferences, topicListingTable) { if (!$chk(this.labelElement())) { return; } //ucpInsertionIdx; // TODO: move to UCPTopicListingTable to make it work again as before if (groupPreferences.ucpSort()) { // move VOTE threads to the top, closed and voted threads to the bottom var moveUp = undefined, moveDown = undefined; var newchlgstatus = this.challengeStatus(); moveUp = (newchlgstatus === "VOTE" || newchlgstatus === "--VOTE--" || newchlgstatus === "PlayerMustVote"); moveDown = (newchlgstatus === "---" || newchlgstatus === "Excluded" || newchlgstatus === "Voted" || newchlgstatus === "Closed" || newchlgstatus === "Finished" || newchlgstatus === "PlayerFinished" || newchlgstatus === "PlayerVoted"); if (moveUp && ucpInsertionIdx === 0 && // if we already have moved a row, it should be fine $$('a[name^=infinitepage]').length > 0) { var autopageWarning = $('UCheckPlayAutoPageWarning'); if (!autopageWarning) { // only show it once! new Element('p', { id: "UCheckPlayAutoPageWarning", html: "UCheckPlay: moving rows up when AutoPage is active may create chaos! " + "Skipping.<br/>" + "You may consider turning of the AutoPage script for this page, or " + "not using the UCPstyle for this group", styles: { textDecoration: 'none', color: 'brown', display: 'block' } }).inject('UCheckPlayStatusDiv'); $('UCheckPlayStatusDiv').setStyle('display', 'block'); } } else if (moveUp || moveDown) { if (topicListingTable) { var tableRows = topicListingTable.getElements('tr'); if (tableRows && tableRows.length > 0) { var row = this.labelElement().getParent('tr'); if (row) { if (moveUp) { var previousRow = tableRows[ucpInsertionIdx++]; // when 0, inserts after header $(row).inject(previousRow, 'after'); } else if (moveDown) { // don't move the last row down, after itself! // to prevent this from happening, first add a dummy row, and remove it afterwards if (row !== topicListingTable.getLast()) { $(row).inject(topicListingTable.getLast(), 'after'); } } } } } } } }, checkStatus: function (photosposted, debug) { if (debug) GM_log("checkStatus: chlgstatus = '" + this.challengeStatus() + "', waiting state: '" + this.groupConfig().states().waitingForEntries + "', challenge name: '" + this.challengeName()); if (this.challengeName().match(this.groupConfig().states().closed)) { // GM_log("name: " + this.challengeName() + " matches " + this.groupConfig().states().closed); this.updateStatus("closed"); } if (this.challengeName().match(this.groupConfig().states().voided)) { this.updateStatus("voided"); } if ((this.challengeStatus() === "none") && this.filled(photosposted)) { this.updateStatus("filled"); } if ((this.challengeStatus() === "none") && this.open(photosposted)) { this.setChallengeStatus("Open"); } if ((this.challengeStatus() === "none") && this.waitingForEntries(photosposted)) { this.setChallengeStatus("OK"); } if ( (this.challengeStatus() === "none" || this.challengeStatus() === "Excluded") && $chk(this.challengeName().match(this.groupConfig().states().vote))) { if (debug) GM_log("setting to --VOTE--: " + this.challengeName() + " matches " + this.groupConfig().states().vote); this.setChallengeStatus("--VOTE--"); } if ((this.challengeStatus() === "Player") && (this.filled(photosposted) || this.challengeName().match(this.groupConfig().states().vote))) { this.updateStatus(this.challengeDefinition().playerVoting()); } if (this.challengeStatus() === "PlayerMayVote" || this.challengeStatus() === "Player") { if (this.filled(photosposted)) { this.setChallengeStatus("PlayerFilled"); } return; } }, resetStatus: function () { this.setChallengeStatus("none"); this.votingErrors = []; }, loadthread: function(groupPreferences, processDiscussionTopicCallback) { // var ifModifiedSince = ucpThread.lastLoadTime() ? new Date(ucpThread.lastLoadTime() * 1000) : new Date(0); //maybe we could use if-modified-since header to improve performance /*var threadNr = this.challengeName().match(/^\d+/); if ($chk(threadNr)) { this.startload = new Date(); }*/ var ucpThread = this; new Request({ url: ucpThread.url(), async: true, headers: { //"User-Agent": "Mozilla/5.0", // If not specified, navigator.userAgent will be used. "Accept": "text/html", // If not specified, browser defaults will be used. // "If-Modified-Since": ifModifiedSince.toUTCString() // does not work on flickr :( }, onFailure: function (response) { GM_log("error loading " + ucpThread.challengeName()); ucpThread.resetStatus(); ucpThread.setChallengeStatus("ERRORLOADING"); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); }, onSuccess: function (responseText, responseXML) { /*if ($chk(threadNr)) { ucpThread.pageloaded = new Date(); }*/ // GM_log("headers: " + this.xhr.headers); this.cancel(); // stop downloading images ucpThread.setLastLoadTime(Math.round((new Date()).getTime() / 1000)); var challengeConfig = ucpThread.challengeDefinition(); var tempDiv = new Element('div', { html: responseText.stripScripts() }); // getElement with id, or getElementById does not work on tempDiv !? var discussionTopic = tempDiv.getElement('div[id=DiscussTopic]'); // multiple pages? // getElement with id, or getElementById does not work on tempDiv !? var goodStuff = tempDiv.getElement('td[id=GoodStuff]'); try { var nextButton = goodStuff.getElement('div.Paginator').getElement('a.Next'); } catch (e) { //GM_log("error getting Next button: " + e); } ucpThread.loadNextTopicPage($chk(nextButton) ? nextButton.href : null, discussionTopic, processDiscussionTopicCallback); } // onload }).get(); // xmlHttpRequest }, // loadthread loadNextTopicPage: function(nextPageUri, discussionTopic, processDiscussionTopicCallback) { var ucpThread = this; if (!nextPageUri) { // the last page try { processDiscussionTopicCallback(discussionTopic, this); } catch (e) { GM_log("error processing (callback): " + e); } try { ucpThread.store(); } catch (e) { GM_log("error storing" + e); } // TODO: photo-summary for multipage voting //ucpThread.printStatus(groupPreferences); => prints score summary twice! return; } new Request({ method: "get", url: nextPageUri, async: false, onFailure: function (response) { GM_log("error loading " + nextPageUri); ucpThread.setChallengeStatus("ERRORLOADING"); processDiscussionTopicCallback(discussionTopic, ucpThread); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); }, onSuccess: function (responseText, responseXML) { this.cancel(); // stop downloading images var discussionHTML = responseText; var tempDiv = new Element('div', { html: discussionHTML.stripScripts() }); // getElement with id, or getElementById does not work on tempDiv !? var tempTable = tempDiv.getElement('div[id=Main]').getElement('td[id=GoodStuff]').getElement('table.TopicReply'); var tempBody = tempTable.getElement('tbody'); var tempRows = tempBody.getElements('tr'); var firstPageRepliesTableBody = $(discussionTopic).getElement('table.TopicReply tbody'); firstPageRepliesTableBody.adopt( new Element('tr').adopt( new Element('td', { colSpan: 2, html: 'Next page:' }) ) ); tempRows.each(function (row) { row.dispose(); row.inject(firstPageRepliesTableBody); }); // multiple pages? // only for challenge threads // => wrong! multiple page games, showrooms would not have a comment form // => wrongly considered close // getElement with id, or getElementById does not work on tempDiv !? var goodStuff = tempDiv.getElement('td[id=GoodStuff]'); if ($chk(goodStuff.getElement('div.Paginator'))) { var nextButton = goodStuff.getElement('div.Paginator').getElement('a.Next'); } ucpThread.loadNextTopicPage($chk(nextButton) ? nextButton.href : null, discussionTopic, processDiscussionTopicCallback); } }).send(); }, collectVotes: function (challengeAnnouncement, challengeEntries) { return ucpCollectVotes(this, this.groupConfig().allowsPhotoInAnnouncement() ? challengeAnnouncement : null, challengeEntries ); } }); var UCPNonChallengeThread = new Class({ Extends: UCPThread, initialize: function (options) { this.parent(options); }, printStatus: function (groupPreferences, ucpLanguage, newchlgstatus) { if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage(groupPreferences.language(), groupPreferences.useLegacyLabels() && groupPreferences.groupConfig().hasLegacyLabels() ? groupPreferences.groupConfig().legacyLabels() : undefined, groupPreferences.groupConfig().languageOverrides()); } if (!$chk(ucpLanguage)) { ucpLanguage = new UCPLanguageConfigReader().createLanguage('English', groupPreferences.useLegacyLabels() && groupPreferences.groupConfig().hasLegacyLabels() ? groupPreferences.groupConfig().legacyLabels() : undefined, groupPreferences.groupConfig().languageOverrides()); } var anchortitle = ucpLanguage.titles()[this.getLabelPrefix()]; if (this.labelElement()) { this.labelElement().set('html', ucpLanguage.labels()[this.getLabelPrefix()]); this.labelElement().set ('style', 'color: ' + ucpLanguage.labels()[this.getLabelPrefix() + "Color"] + '; text-decoration: none'); this.labelElement().title = anchortitle; this.challengeAnchor().title = anchortitle; } }, getLabelPrefix: function () {}, collectVotes: function () { return { votes: [], comments: [], photos: [] }; } }); // TODO: use new UCPNonChallengeThread({status: 'Chat', labelPrefix: 'chat'}); var UCPChatThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Chat"); }, getLabelPrefix: function () { return "chat"; } }); var UCPShowroomThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Showroom"); }, getLabelPrefix: function () { return "showroom"; } }); var UCPGameThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Game"); }, getLabelPrefix: function () { return "game"; } }); var UCPMeetAndGreetThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("MeetAndGreet"); }, getLabelPrefix: function () { return "meetAndGreet"; } }); var UCPInformationThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Info"); }, getLabelPrefix: function () { return "info"; } }); /*var UCPVoidedThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Voided"); }, getLabelPrefix: function () { return "ignore"; } });*/ var UCPUnknownThread = new Class({ Extends: UCPNonChallengeThread, initialize: function (options) { this.parent(options); }, resetStatus: function () { this.parent(); this.setChallengeStatus("Unknown"); }, getLabelPrefix: function () { return "ignore"; } }); var UCPChallengeThread = new Class({ Extends: UCPThread, initialize: function(options) { this.parent(options); }, filled: function (photosposted) { if (this.challengeDefinition().neededPhotos() <= 0) { return false; } if (photosposted >= this.challengeDefinition().neededPhotos() && (this.challengeName().match(this.groupConfig().states().open) || this.challengeName().match(this.groupConfig().states().waitingForEntries))) { return true; } return false; }, open: function (photosposted) { //GM_log("checking name against open states: " + this.chlgname + " - " + states.open + " - " + states.waitingForEntries); if (photosposted === 0 && (this.challengeName().match(this.groupConfig().states().open) || this.challengeName().match(this.groupConfig().states().waitingForEntries))) { return true; } return false; }, waitingForEntries: function (photosposted) { if (!$chk(photosposted)) { return false; } if (this.challengeDefinition().neededPhotos() < 0 && (this.challengeName().match(this.groupConfig().states().open) || this.challengeName().match(this.groupConfig().states().waitingForEntries))) { return true; } if (this.challengeDefinition().neededPhotos() < 0) { return false; } return (photosposted < this.challengeDefinition().neededPhotos() && (this.challengeName().match(this.groupConfig().states().waitingForEntries) || this.challengeName().match(this.groupConfig().states().open))); }, finished: function(vote) { // special case: PIC-P requires a diff of 2 points if (this.challengeDefinition().scoreType().match(/PIC-P-/)) { if (vote.topDiff() >= 2 && this.challengeDefinition().scoresAdded() && vote.maxVote >= this.challengeDefinition().neededScore()) { return true; } return false; } if (this.challengeDefinition().neededScore() === -1) { return false; } if (this.challengeDefinition().scoresAdded() && vote.maxVote >= this.challengeDefinition().neededScore()) { return true; } if (!this.challengeDefinition().scoresAdded() && vote.isUnanimous()) { return true; } return false; }, resetStatus: function() { this.parent(); this.checkStatus(); } }); UCPStylePreferences = new Class({ Implements: [Options], options: { removeNew: true, firstColumn: true, sort: true }, initialize: function (group, options) { this.setOptions(options); if (!options) { var storedRemoveNew = GM_getValue("UCP.ucpStyle.removeNew." + group.groupname()); this.options.removeNew = (storedRemoveNew === true || storedRemoveNew === 'true'); var storedFirstColumn = GM_getValue("UCP.ucpStyle.firstColumn." + group.groupname()); this.options.firstColumn = (storedFirstColumn === true || storedFirstColumn === 'true'); var storedSort = GM_getValue("UCP.ucpStyle.sort." + group.groupname()); this.options.sort = (storedSort === true || storedSort === 'true'); } }, equals: function (other) { if (!other) { return false; } return this.options.removeNew === other.options.removeNew && this.options.firstColumn === other.options.firstColumn && this.options.sort === other.options.sort; }, store: function (group) { var ucpStyleRemoveNew = GM_setValue("UCP.ucpStyle.removeNew." + group.groupname(), this.options.removeNew); var ucpStyleFirstColumn = GM_setValue("UCP.ucpStyle.firstColumn." + group.groupname(), this.options.firstColumn); var ucpStyleSort = GM_setValue("UCP.ucpStyle.sort." + group.groupname(), this.options.sort); } }); UCPGroupPreferences = new Class({ Implements: [Options], options: { groupConfig: null, // mandatory // UCheckPlay preferences ucpStyle: null, // defaults to flickrStyle language: 'English', useLegacyLabels: false }, initialize: function (options) { this.setOptions(options); var groupname = this.options.groupConfig.groupname(); // layout style if (!$chk(options.ucpStyle)) { var storedUCPStyle = GM_getValue("UCP.ucpStyle." + groupname); var ucpStyle = (storedUCPStyle === true || storedUCPStyle === 'true'); if (ucpStyle) { this.options.ucpStyle = new UCPStylePreferences(this.groupConfig()); } } // language if (!$chk(options.language)) { var storedLanguage = GM_getValue("UCP.language." + groupname); if (storedLanguage) { this.options.language = storedLanguage; } } // legacy labels if (!$chk(options.useLegacyLabels)) { if ($chk(GM_getValue("UCP.useLegacyIcons." + groupname))) { this.options.useLegacyLabels = GM_getValue("UCP.useLegacyIcons." + groupname); } } }, groupConfig: function () { return this.options.groupConfig; }, setStyle: function (styleOptions) { var retval = false; // something changes? if (styleOptions.ucpStyle === true) { if (this.ucpStyle === null) { retval = true; } var oldStyle = this.options.ucpStyle; this.options.ucpStyle = new UCPStylePreferences(this.groupConfig(), { removeNew: styleOptions.ucpStyleRemoveNew, firstColumn: styleOptions.ucpStyleFirstColumn, sort: styleOptions.ucpStyleSort }); this.options.ucpStyle.store(this.groupConfig()); retval = retval || !this.options.ucpStyle.equals(oldStyle); GM_setValue("UCP.ucpStyle." + this.groupConfig().groupname(), true); } else { if (this.options.ucpStyle !== null) { retval = true; } this.options.ucpStyle = null; GM_setValue("UCP.ucpStyle." + this.groupConfig().groupname(), false); } return retval; }, setLanguage: function (language) { if (language !== this.language()) { GM_setValue("UCP.language." + this.groupConfig().groupname(), language); this.options.language = language; return true; } return false; }, setUseLegacyLabels: function (useLegacyLabels) { if (useLegacyLabels !== this.options.useLegacyLabels) { GM_setValue("UCP.useLegacyIcons." + this.groupConfig().groupname(), useLegacyLabels); this.options.useLegacyLabels = useLegacyLabels; return true; } return false; }, useLegacyLabels: function () { return this.options.useLegacyLabels || this.options.groupConfig.mandatoryGroupLabels(); }, // shortcuts language: function() { return this.options.language; }, ucpStyle: function () { return $chk(this.options.ucpStyle); }, ucpFirstColumn: function () { return this.ucpStyle() && this.options.ucpStyle.options.firstColumn; }, ucpRemoveNew: function () { return this.ucpStyle() && this.options.ucpStyle.options.removeNew; }, ucpSort: function () { return this.ucpStyle() && this.options.ucpStyle.options.sort; } }); function ucpCheckPhoto(photoNode, index, array) { // expects 'this' to be of type UCPChallengeDefinition try { var src = photoNode.getAttribute('src'); if (!$chk(src) || src.length === 0) { return false; } if (photoNode.getAttribute('alt') === 'UCPthumbnail') { return false; } if (src.contains("buddyicons")) { return false; } if (!src.contains("static.flickr.com")) { return false; } if (!this.iconChallenge()) { // ignore the height attribute: Flickr adds class="notsowide": // img.notsowide: { // height: auto; // max-width: 500px; // } // => even if specified, 'height' does nothing var width = photoNode.getAttribute('width'); // this.width returns bogus info if attribute 'width' is missing if ($chk(width) && width < 275) { return false; } // thumbnails, square, .. from flickr are not medium if (src.match(/_t.jpg$|_s.jpg$/)) { return false; } } if ($chk(groupConfig)) { if ($chk(groupConfig.nonPhotoImages()[src])) { return false; } } return true; } catch (e) { GM_log("error checking photo: " + e); return false; } return false; }; function ucpAddCPheader() { new Element('span', { html: 'UNIFIED CP ' + CPtoolversion + ' - ', title: 'UNIFIED Challenges CheckPlay Tool ' + CPtoolversion, }).inject($("TopBar").getElement("td.Status"), 'top'); } // from http://pmav.eu/stuff/javascript-hashing-functions/source.html function ucpUniversalHash(s, tableSize) { if (!tableSize) { tableSize = 65534; } var b = 27183, h = 0, a = 31415; if (tableSize > 1) { for (i = 0; i < s.length; i++) { h = (a * h + s[i].charCodeAt()) % tableSize; a = ((a % tableSize) * (b % tableSize)) % (tableSize); } } return h; } function ucpAddVotingError(ori, extra) { if (ori === null) { return extra; } else { return ori + " | " + extra; } } var UCPVote = new Class({ Implements: [Options], options: { chlgname: null, ucpThread: null, node: null, poster: null, voteText: null, votesArray: [], messages: [], inError: false, errorIdx: null }, initialize: function(options) { this.setOptions(options); this.maxVote = 0; this.votedFor = 0; if (options.votesArray !== null) { for (var oIdx = 0, oLen = options.votesArray.length; oIdx < oLen; oIdx++) { if (!isNaN(options.votesArray[oIdx])) { this.maxVote = Math.max(options.votesArray[oIdx], this.maxVote); this.options.votesArray[oIdx] = options.votesArray[oIdx]; } else { this.options.votesArray[oIdx] = 0; } } } }, poster: function () { return this.options.poster; }, node: function () { return this.options.node; }, ucpThread: function () { return this.options.ucpThread; }, calculateVotedFor: function (scoresAdded) { // is only called for a first vote if (scoresAdded) { for (var oIdx = 1, oLen = this.options.votesArray.length; oIdx < oLen; oIdx++) { if (this.options.votesArray[oIdx] > 0) { this.votedFor = oIdx; break; } } } else { for (var oIdx = 1, oLen = this.options.votesArray.length; oIdx < oLen; oIdx++) { if (this.options.votesArray[oIdx] < this.maxVote) { this.votedFor = oIdx; break; } } } return this.votedFor; }, valid: function (previousVote, scoresAdded) { /*if (debug) { GM_log(['comparing in ' + this.options.chlgname, ' ' + this.toString(), ' with ', previousVote.toString() ].join('\n')); }*/ if (!(previousVote instanceof UCPVote)) { return true; } if (this.options.votesArray.length !== previousVote.options.votesArray.length) { this.addError("voted for " + (this.options.votesArray.length - 1) + " photos while '" + previousVote.poster().username + "' voted for " + (previousVote.options.votesArray.length - 1)); return false; } if (this.options.votesArray.length === 0) { return true; } this.votedFor = 0; // photos start at 1 //for (var oIdx = 0, oLength = this.options.votesArray.length; oIdx < oLength; ++oIdx) { this.options.votesArray.each( function (vote, oIdx) { if (oIdx === 0) { return; } if (isNaN(vote) || isNaN(previousVote.options.votesArray[oIdx])) { return; } var diff = vote - previousVote.options.votesArray[oIdx]; if ((diff < 0 && scoresAdded) || (diff > 0 && !scoresAdded)) { this.addError("dropped a vote"); this.options.errorIdx = oIdx; return; } if ((diff > 1 && scoresAdded) || (diff < -1 && !scoresAdded)) { this.addError("voted too much on same photo"); this.options.errorIdx = oIdx; this.votedFor = oIdx; return; } if (this.votedFor > 0 && diff !== 0) { this.addError("misvoted"); this.options.errorIdx = oIdx; return; } if (this.votedFor <= 0 && diff !== 0) { this.votedFor = oIdx; } }, this); if (this.votedFor <= 0) { this.addError("did not really vote"); this.options.errorIdx = 0; return false; } /* ego-vote removal if (this.votedFor > 0 && this.votedFor > numberOfPhotos) { this.error = ucpAddVotingError(this.error, "'" + this.poster.username + "' voted for a non-existing photo"); if (this.errorIdx < 1) { this.errorIdx = this.votedFor; } } */ return this.votedFor > 0 && this.options.errorIdx < 1; }, isUnanimous: function () { var scoreIdx = 0; for (var oIdx = 1, oLen = this.options.votesArray.length; oIdx < oLen; ++oIdx) { if (!isNaN(this.options.votesArray[oIdx]) && this.options.votesArray[oIdx] > 0) { if (scoreIdx === 0) { // first fote found scoreIdx = oIdx; } else { if (scoreIdx !== oIdx) { return false; } } } } return scoreIdx !== 0; // no score found = no voting = not unanimous }, isExampleVote: function (scoresAdded, neededScore) { if (scoresAdded) { return this.maxVote === 0; } // !scoresAdded if (this.maxVote !== neededScore) { // fast test return false; } for (var oIdx = 1, oLen = this.options.votesArray.length; oIdx < oLen; ++oIdx) { if (isNaN(this.options.votesArray[oIdx])) { return false; } if (this.options.votesArray[oIdx] !== neededScore) { return false; } } return true; }, isVoid: function () { return false; }, add: function (other) { other.options.votesArray.each(function (vote, oIdx) { if (oIdx === 0) { return; } if (vote && !isNaN(vote)) { if (this.options.votesArray[oIdx] && !isNaN(this.options.votesArray[oIdx])) { this.options.votesArray[oIdx] += vote; } else { this.options.votesArray[oIdx] = vote; } if (this.options.votesArray[oIdx] > this.maxVote) { this.maxVote = this.options.votesArray[oIdx]; } } }, this); }, topDiff: function () { // returns the difference between the two top scores var secondBest = 0; // this.maxVote should be set, but we don't have its index var theBest = 0; this.options.votesArray.each(function (score, oIdx) { if (oIdx === 0) { return; } if (score > theBest) { secondBest = theBest; theBest = score; } else if (score > secondBest) { secondBest = score; } }, this); return theBest - secondBest; }, toString: function () { var retval = this.options.poster.username + ' ' + this.options.voteText + ' =('; this.options.votesArray.each(function (vote) { retval = retval + ' ' + vote; }, this); retval = retval + ') => ' + this.maxVote; return retval; }, showVotes: function () { var retval = undefined; this.options.votesArray.each(function (vote, oIdx) { if (oIdx === 0 || isNaN(vote) || vote <= 0) { return; } var part = oIdx + ": " + vote; if (this.options.errorIdx === oIdx) { part = "<b>" + part + "</b>"; } if (retval === undefined) { retval = part; } else { retval = retval + ", " + part; } }, this); return "(" + retval + ")"; }, showPicVotes: function () { var retval = ""; this.options.votesArray.each(function (vote, oIdx) { if (oIdx === 0) { return; } if (!isNaN(vote) && vote > 0) { retval = retval + (retval.length > 0 ? ", " : "") + oIdx; } }, this); return "(" + retval + ")"; }, showPicResult: function () { // first, sort the result var sorted = []; var vote; for (var oIdx = 1, oLen = this.options.votesArray.length; oIdx < oLen; ++oIdx) { vote = this.options.votesArray[oIdx]; if (!isNaN(vote)) { var added = false; for (var sIdx = 0, sLen = sorted.length; sIdx < sLen; ++sIdx) { if (sorted[sIdx].value <= vote) { // insert element => move the rest to the end for (var rIdx = sorted.length; rIdx > sIdx; --rIdx) { sorted[rIdx] = sorted[rIdx - 1]; } sorted[sIdx] = { photo: oIdx, value: vote }; added = true; break; } } if (!added) { sorted[sorted.length] = { photo: oIdx, value: vote }; } } } var retval = ""; sorted.each(function (vote) { retval = retval + (retval.length > 0 ? ", " : "") + vote.photo + ":" + vote.value; }, this); return "(" + retval + ")"; }, messages: function() { return this.options.messages; }, addMessage: function (message) { this.options.messages.include({msg: message, type: 'message'}); }, addWarning: function (warning) { this.options.messages.include({msg: warning, type: 'warning'}); }, addError: function (error) { this.options.messages.include({msg: error, type: 'error'}); }, error: function() { var retval = ''; this.options.messages.each(function (message) { if (message.type === 'error') { retval = retval + " - " + message.msg; } }); return retval; }, printStatus: function () { if (this.options.ucpThread.options.needsStatusOnChallengePage) { if ($chk(this.messages()) && this.messages().length > 0) { var pageAnchor = this.node().getElements('small').getLast(); if ($chk(pageAnchor) && $chk(pageAnchor.getParent('div.ucpdiv'))) { pageAnchor = pageAnchor.getParent('div.ucpdiv'); } if (!$chk(pageAnchor)) { pageAnchor = this.node(); } var commentAnchor = new Element('small', { html: "UCheckPlayNG: " }).inject(new Element('div', { 'class': 'ucpdiv' }).inject(pageAnchor, 'after')); this.messages().each(function (message, idx) { var msg = (idx > 0 ? " - " : "") + message.msg; var color = message.type === 'error' ? 'red' : message.type === 'warning' ? 'orange' : ''; commentAnchor.adopt(new Element('span', { html: msg, styles: { color: color } })); }); } } } }); function ucpCheckPhotoApproval(photo, topic) { // returns // { // approved: true/false, // approver: string, // version: number, // checksum: true/false, // photoChecksum: true/false, // error: string // photoId: string // } var photoRow = photo.getParent('tr'); var photoTextNode = photoRow.getElement('td.Said p'); var photoId = photo.get('src').match(/http:\/\/.*flickr.com\/\d+\/(\d+)_.*/)[1]; /*return { approved: true, approver: "todo", photoId: photoId }; */ try { var approvedNode = photoTextNode.getElement("img[src=http://l.yimg.com/g/images/spaceout.gif][alt*=UCPAapproved]"); } catch (e) { GM_log("error: " + e); return true; } if (!approvedNode) { return { approved: false }; } var approvedString = approvedNode.get('alt'); var name, photoChecksum, checksum, version; try { var approveMatch = /UCPAapproved:([^:]*):([^:]*):([^:]*):(\d+)/.exec(approvedString); name = decodeURIComponent(approveMatch[1]); photoChecksum = approveMatch[2]; checksum = approveMatch[3]; version = approveMatch[4]; } catch (e) { return { approved: false, error: "checksum has been modified!", photoId: photoId }; } if ($chk(approveMatch)) { var goodPhotoChecksum = ucpUniversalHash(photo.src); if (goodPhotoChecksum !== parseInt(photoChecksum, 10)) { return { approved: false, version: version, checksum: true, photoChecksum: false, error: "photo has been changed after approval!", photoId: photoId }; } var goodChecksum = ucpUniversalHash(photoId + name + topic); //GM_log("good: " + goodChecksum + " - photo: " + checksum); if (goodChecksum !== parseInt(checksum, 10)) { return { approved: false, version: version, checksum: false, error: "checksum has been tampered with!", photoId: photoId }; } return { approved: true, approver: name, version: version, checksum: true, photoChecksum: true, photoId: photoId }; } else { return { approved: false, error: "failed to process UCPA approved entry"}; } } var ucpReHorizontalVoteMatch; function ucpGetReHorizontalVoteMatch() { // TODO: if challengeConfig.neededPhotos === ucpThread.photosposted => limit reMatch to nPhotos if (!ucpReHorizontalVoteMatch) { // when split with a character, it can be double digits var reVoteMatchSplit = "(?:\\b|\\s)(\\d+|[xX]{1})(?:\\s*)?[^xX\\d]{1}(?:\\s*)?(\\d+|[xX]{1})"; // at least 2 // when stuck to each other, it should be single digits var reVoteMatchJoined = "(?:\\b|\\s)(\\d|[xX]{1})(\\d|[xX]{1})"; // at least 2 // limit the seperation characters to non-alphabet characters: for (var p = 1; p < 10; ++p) { reVoteMatchSplit = reVoteMatchSplit + "(?:\\s*)?(?:[^xX\\d]{1})?(?:\\s*)?(\\d+|[xX]{1})?"; reVoteMatchJoined = reVoteMatchJoined + "(\\d|[xX]{1})?"; } reVoteMatchSplit = reVoteMatchSplit + "(?:\\s|\\b)"; reVoteMatchJoined = reVoteMatchJoined + "(?:\\s|\\b)"; ucpReHorizontalVoteMatch = new RegExp(reVoteMatchSplit + "|" + reVoteMatchJoined, "ig"); } return ucpReHorizontalVoteMatch; } function ucpCreateHorizontalVote(picType, exampleVote, ucpThread, poster, challengeEntry, replytxt) { // first: cleanup the comment, to ease the vote recognition // in The mother of all challenge group, the 'local' CP script has no way to handle '10'; // they use ten instead // people that use this hack, also use it in other groups: replytxt = replytxt.replace(/[\s\b-,]ten[\s\b-,]/g, 10); replytxt = replytxt.replace(/nine/g, 9); replytxt = replytxt.replace(/eight/g, 8); replytxt = replytxt.replace(/seven/g, 7); replytxt = replytxt.replace(/six/g, 6); replytxt = replytxt.replace(/five/g, 5); replytxt = replytxt.replace(/four/g, 4); replytxt = replytxt.replace(/three/g, 3); replytxt = replytxt.replace(/two/g, 2); replytxt = replytxt.replace(/one/g, 1); replytxt = replytxt.replace(/nil/g, 0); replytxt = replytxt.replace(/zero/g, 0); var replyLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? var horizontalVotes = null; $each(replyLines, function (replyLine) { if (replyLine.match(/void/i)) { // Skip lines where the vote has been voided return; } // check for comments before any other manipulations // corrections of the form 'corrected:', 'correction:' (need the ':' -> '1-2-3 with correction') replyLine = replyLine.replace(/cor(?:r)?ected:|correction:/, '>>'); // corrected sometimes mis-spelled // removed: vote after misvote '1-2-3 (with correction)' where seen as regular comments if (!replyLine || replyLine.replace(/<[^>]*>/g, '') // remove html tags .replace(/^\s*/g, '') // remove leading spaces .replace(/^foto[^\d]*|^photo[^\d]*/i, '') // remove 'photo' in 'photo 2: 1-1-1' .replace(/(>)+/, '') // don't ignore corrections (>> or --->) .match(/^[^\d]{6}/)) { // 6? arbitrary return; } replyLine = replyLine.replace(/<a[^<]*<\/a>/g, ''); // removes links replyLine = replyLine.replace(/\s*<[^>]*>\s*/g, ''); // removes html tags // remove any leading white space replyLine = replyLine.replace(/^\s*/g, ''); replyLine = replyLine.replace(/^foto[^\d]*|^photo[^\d]*/i, ''); if (!replyLine) { return; } replyLine = replyLine.replace(/^.*(?:>|\||→)+\s*/g, ''); // correction // some like voting with extra spaces '0 - 1 - 2' // must be done after correction characters //replyLine = replyLine.replace(/(\d+)\s+[^a-zA-Z0-9]/g, "$1-"); // remove the trailing '-' replyLine = replyLine.replace(/-$/, ''); // ignore lines that are comment text if (!replyLine || replyLine.match(/^\s*[a-zA-Z\s\.]{6}/)) { // 6? arbitrary return; } // catches multiple votes, or comment, on multiple lines var matchVotes = ucpGetReHorizontalVoteMatch().exec(replyLine); while (matchVotes) { // catches multiple votes (correction?) on one line //GM_log("found " + matchVotes); horizontalVotes = matchVotes; matchVotes = ucpGetReHorizontalVoteMatch().exec(replyLine); } }); if (horizontalVotes) { var nvotes = horizontalVotes.length - 1; // vote: 1-2-3 // if (nvotes >= neededPhotos) { var hVotesArray = []; var voteIdx = 1; // 0: the input string while (!horizontalVotes[voteIdx]) { ++voteIdx; // skip the non matching part } var arrayIdx = 1; while (true) { if (picType) { hVotesArray[parseInt(horizontalVotes[voteIdx], 10)] = 1; } else { hVotesArray[arrayIdx] = parseInt(horizontalVotes[voteIdx], 10); } if (voteIdx + 1 >= horizontalVotes.length) { break; } if (!horizontalVotes[voteIdx + 1]) { break; } ++voteIdx; ++arrayIdx; } var retval = new UCPVote({ chlgname: ucpThread.challengeName(), poster: poster, voteText: horizontalVotes, votesArray: hVotesArray, node: challengeEntry, ucpThread: ucpThread }); if (exampleVote) { retval.isExampleVote = function (a,b) { return true; }; } return retval; /* } else { // No votes found in this reply } */ } } // The challenger or competitor's photo in one of the challenges. var UCPCompetitor = new Class({ Implements: [Options], options: { node: null, photo: null, poster: null, owner: null, photoId: null, comments: [], ucpThread: null }, initialize: function (options) { this.setOptions(options); }, ucpThread: function () { return this.options.ucpThread; }, toString: function () { return this.options.poster.username; }, // accessors node: function () { return this.options.node; }, photo: function () { return this.options.photo; }, poster: function () { return this.options.poster; }, owner: function () { return this.options.owner; }, photoId: function () { return this.options.photoId; }, error: function () { return this.options.comments.some(function (comment) { if (comment.type === 'error') { return true; } return false; }); }, printStatus: function () { if (this.options.ucpThread.options.needsStatusOnChallengePage) { var pageAnchor = this.node().getElements('small').getLast(); if ($chk(pageAnchor) && $chk(pageAnchor.getParent('div.ucpdiv'))) { pageAnchor = pageAnchor.getParent('div.ucpdiv'); } if (!$chk(pageAnchor)) { pageAnchor = this.options.node; } var message = "UCheckPlayNG: found a photo posted by <b>" + this.poster().username + "</b>"; var commentAnchor = new Element('small', { html: message }).inject(new Element('div', { 'class': 'ucpdiv' }).inject(pageAnchor, 'after')); // ucpCompetitor.checkForValidPoster(); not! already done in processing of collectVotes this.options.comments.each(function (comment) { commentAnchor.adopt( new Element('span', { html: " - " + comment.msg, styles: { color: comment.type === 'comment' ? '': comment.type === 'warning' ? 'orange' : this.poster().admin ? 'orange' : 'red' } }) ); }, this); } }, addError: function (error) { this.options.comments.include({ msg: error, type: 'error' }); }, addWarning: function (warning) { this.options.comments.include({ msg: warning, type: 'warning' }); }, addComment: function (comment) { this.options.comments.include({ msg: comment, type: 'comment' }); }, checkForValidPoster: function () { if ($chk(this.owner()) && (this.poster().userid !== this.owner().userid)) { // get the real photo owner's id var apiData = { api_key: GM_getMagisterLudi(), auth_hash: GM_getAuthHash(), auth_token: GM_getAuthToken(), format: 'json', method: 'flickr.photos.getInfo', nojsoncallback: 1, photo_id: this.photoId() }; var realOwnerId, realUsername; new Request({ url: "http://api.flickr.com/", async: false, onSuccess: function (responseText, responseXML) { var result; try { result = JSON.parse(responseText); } catch (e) { result = eval('(' + responseText + ')'); } if (result.stat === 'fail') { return; } realOwnerId = result.photo.owner.nsid; realUsername = result.photo.owner.username; } }).get("/services/rest/", apiData); this.owner().userid = realOwnerId; this.owner().username = realUsername; var buddyicon = this.photo().getParent('td.Said').getParent('tr').getElement('td.Who a img'); var realPosterId = /static.flickr.com\/\d+\/buddyicons\/(\d+@\w\d+)\.jpg/.exec(buddyicon.get('src')) if ($chk(realPosterId)) { this.poster().userid = realPosterId[1]; } return this.poster().userid === this.owner().userid; } return true; } }); var UCPVoteComment = new Class({ Implements: [Options], options: { ucpThread: null, poster: null, comments: [], node: null }, initialize: function (options) { this.setOptions(options); }, toString: function () { return this.options.poster.username; }, poster: function () { return this.options.poster; }, node: function() { return this.options.node; }, ucpThread: function () { return this.options.ucpThread; }, printStatus: function () { var pageAnchor = this.node().getElements('small').getLast(); if ($chk(pageAnchor) && $chk(pageAnchor.getParent('div.ucpdiv'))) { pageAnchor = pageAnchor.getParent('div.ucpdiv'); } if (!$chk(pageAnchor)) { pageAnchor = this.options.node; } if (this.options.ucpThread.challengeDefinition().scoreType() === "MEETANDGREET") { var message = "found a greeting from <b>" + this.poster().username; } else { if ($chk(this.options.comments) && this.options.comments.length > 0) { this.options.comments.each(function (comment) { message = ($chk(message) ? " - " : "") + comment.msg; // TODO: type: error }); } else { message = "found a regular comment (no photo, no votes)"; } message = message + " from " + this.poster().username; } new Element('small', { html: "UCheckPlayNG: " + message + "<br/>" }).inject(new Element('div', { 'class': 'ucpdiv' }).inject(pageAnchor, 'after')); } }); function ucpCreateCompetitor(photoNode, poster, ucpThread) { var ownerId; var photoId = photoNode.get('src').match(/http:\/\/.*flickr.com\/\d+\/(\d+)_.*/)[1] if (photoNode.getParent('a') && photoNode.getParent('a').get('href')) { ownerId = photoNode.getParent('a').get('href').split('/')[4]; //GM_log("ownerId: " + ownerId); var owner; if (ownerId === poster.userid) { owner = poster; } else { owner = { username: "", userid: ownerId }; } var textNode = photoNode.getParent('td.Said').getElement('p'); var approvalImg = textNode.getElement('img[alt*="UCPAapproved"]'); if (approvalImg && ucpThread.topic()) { var approvalCheck = ucpCheckPhotoApproval(photoNode, ucpThread.topic()); // returns // { // approved: true/false, // approver: string, // version: number, // checksum: true/false, // photoChecksum: true/false, // error: string // photoId: string // } var retval; var photoText; var comment; if (approvalCheck.approved) { comment = { msg: " approved by " + approvalCheck.approver, type: 'comment' }; return new UCPCompetitor({ node: photoNode.getParent('td.Said').getElement('p'), photo: photoNode, photoId: photoId, comments: [ comment ], poster: poster, owner: owner, ucpThread: ucpThread }); } else if (approvalCheck.ignored) { comment = { msg: "non competing image (by " + approvalCheck.approver + ")", type: 'comment' }; return new UCPVoteComment({ node: photoNode.getParent('td.Said').getElement('p'), poster: poster, comments: [ comment ], ucpThread: ucpThread }); } else { retval = new UCPCompetitor({ node: photoNode.getParent('td.Said').getElement('p'), photo: photoNode, photoId: photoId, poster: poster, owner: owner, ucpThread: ucpThread }); retval.addError(approvalCheck.error); return retval; } } else { return new UCPCompetitor({ node: photoNode.getParent('td.Said').getElement('p'), photo: photoNode, photoId: photoId, poster: poster, owner: owner, ucpThread: ucpThread }); } } else { comment = { msg: "photo has no link to photo page", type: 'error' }; return new UCPCompetitor({ node: photoNode.getParent('td.Said').getElement('p'), photo: photoNode, photoId: photoId, poster: poster, comments: [ comment ], ucpThread: ucpThread }); } } function ucpFindPhotos(node, ucpThread, all, poster) { var photoArray = []; if (ucpThread.challengeDefinition().scoreType() === "MEETANDGREET" && !all) { // don't bother return photoArray; } if (!$chk(poster)) { poster = ucpGetUsername(node); } if (!$chk(poster)) { return photoArray; } // there are posters that don't add the 'a' anchor // needs a 'p//a': some scripts add <b> around image //var photos = node.getElement('p').getElements("(a img, img)(not[src*=buddyicons] and [src*=static.flickr.com] and [src$=jpg] and (not[height] or [height > 100]) and (not[width] or [width > 100]))");//, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var photos = node.getElement('p').getElements("img").filter(ucpCheckPhoto, ucpThread.challengeDefinition()); var potentialCompetitor; photos.each(function(photoNode) { if (potentialCompetitor !== undefined && !all) { return; } potentialCompetitor = ucpCreateCompetitor(photoNode, poster, ucpThread); if (potentialCompetitor) { photoArray.push(potentialCompetitor); } }); return photoArray; } function ucpGetUsername(node) { var usernameNode; try { usernameNode = node.getElement('h4 a[href*=photos]'); } catch (e) { if (!usernameNode) { try { usernameNode = node.getParent('td.Said').getElement('h4 a[href*=photos]'); } catch (e) { GM_log("error fetching user name: " + e); } } } if (usernameNode) { var username = usernameNode.textContent.replace(/&/g, "&"); //.replace(/<[^>]*>/g, ''); user '<Jamie>' if (username.length === 0) { GM_log("empty user in element of type " + node); // should not happen! return null; } var userid = usernameNode.href.split('/')[4]; // don't put username in contains part: trouble with special chars: ' " ( ) ... var admin = node.getElement('h4 a img[src*=icon_member_admin]'); if (!admin) { admin = node.getElement('h4 a img[src*=icon_moderator]'); } return { username: username, userid: userid, admin: admin ? true : false }; } else { // deleted users go into a h4, without href try { usernameNode = node.getElement('h4'); } catch (e) { if (!usernameNode) { try { usernameNode = node.getParent('td.Said').getElement('h4'); } catch (e) { GM_log("error fetching user name: " + e); } } } if (!usernameNode) { GM_log("no user"); return null; } var username = usernameNode.textContent.replace(/&/g, "&"); if (username.length === 0) { GM_log("empty user"); return null; } return { username: username, userid: null, admin: false }; } } function ucpCollectVotes(ucpThread, challengeAnnouncement, challengeEntries) { var groupConfig = ucpThread.groupConfig(); var challengeConfig = ucpThread.challengeDefinition(); var topic = ucpThread.topic(); var voteArray = []; var commentArray = []; var photoArray = []; if (challengeAnnouncement) { photoArray.combine(ucpFindPhotos(challengeAnnouncement, ucpThread, false)); /*.each(function (photo) { if ($chk(photo)) { if (photo instanceof UCPCompetitor) { photoArray.include(photo); } } });*/ } challengeEntries.each(function (challengeEntry, i) { if (i === 0) { // announcement return; } if (i === 1 && groupConfig.skipFirstReply()) { return; } //GM_log("["+i+"]: "+challengeEntry.innerHTML); // admins have an image before there name, surrounded with an 'a' tag without 'href' attribute :> skip var poster = ucpGetUsername(challengeEntry); if (!poster) { GM_log("no poster found"); return; } var usercomment = challengeEntry.getElement('p'); if (!$chk(usercomment)) { GM_log("no usercomment in " + challengeConfig.reName()); return; } var photosInNode = ucpFindPhotos(challengeEntry, ucpThread, false, poster); if (photosInNode.length > 0) { photoArray.combine(photosInNode); } else { // This should be a vote or user comment if (i === 2 && groupConfig.skipFirstTwoRepliesForVotes()) { return; } var usercommentClone = $(usercomment.cloneNode(true)); // only consider votes that are after the last photo (only approximate) // if the next comment has a photo, skip! if (voteArray.length === 0) { var nextComment = challengeEntries[i + 1]; if (nextComment && ucpFindPhotos(nextComment, ucpThread, false).length > 0) { commentArray.include(new UCPVoteComment({ node: challengeEntry, poster: poster, ucpThread: ucpThread })); return; } } // first remove striked votes, image icons, banners var strikesAndStuff = usercommentClone.getElements("s, del, strike, img, a"); strikesAndStuff.each(function (strike) { strike.dispose(); }); var replytxt = usercommentClone.innerHTML.split("<small>")[0]; var exampleVote = false; if (replytxt.match(/sample vote|example|ejemplo/i) && !challengeConfig.scoreType().match("PIC-P-")) { // in PIC-P-n commenting is encouraged, often leading to "nice example of", or similar exampleVote = true; } var votes = null; var ignore; switch (challengeConfig.scoreType()) { case "VERTICAL": var vVotesArray = []; var vVoteLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? // voteLines is an array: // #01- 1 // #02- 1+2+3 // .. // others use 1: var reVoteMatch1 = /(\d{1,2})#/; // 01- var reVoteMatch2 = /(\d{1,2})#(\d+)/; // '01-1' var reVoteMatch3 = /(\d{1,2})#(\d+)([^0-9azA-Z](\d+))+/; // '01-1+2' && '01- 1+2+3' var vVotesFound = 0; var vVoided = false; // it misses the first votes: 01-1 or 06-1, but that's to be ignored :) for (var j = 0, vVoteLinesLength = vVoteLines.length; j < vVoteLinesLength; ++j) { var vVoteLine = vVoteLines[j]; // remove any leading spaces vVoteLine = vVoteLine.replace(/^\s*/, ''); // ignore lines that are comment text if (vVoteLine.match(/^\s*[a-zA-Z\s\.\,]{6}/)) { // 6? arbitrary continue; } // first seperate the photo number from the votes, in case it was seperated with a space var reVReplaceMatch = /#(\d{1,2})/; vVoteLine = vVoteLine.replace(reVReplaceMatch, "$1"); // remove leading # reVReplaceMatch = /(\d{1,2})([\-:+ ]+|\.{2,3})/; // faves contest uses '...', adding it to [] creates havoc in other places, even with escaping (or escaping double) vVoteLine = vVoteLine.replace(reVReplaceMatch, "$1#"); // insert # after photonumber // remove spaces vVoteLine = vVoteLine.replace(/\s/g, ''); // remove any character before first vote in voteLine 00#-1+2 vVoteLine = vVoteLine.replace(/(\d{1,2}#)[^0-9]*/, "$1"); if (vVoteLine.match(/^\s*\d{1,2}\s*$/)) { vVoteLine = vVoteLine.replace(/^s*(\d{1,2})\s*$/, "$1#0"); } if (vVoteLine.match(/void/i) && !vVoteLine.match(/^\d/)) { // Skip replies where the vote has been voided vVoided = true; break; } var matchedString, photoNumber, photoScore; var verticalVotes = reVoteMatch3.exec(vVoteLine); if (verticalVotes) { photoNumber = verticalVotes[1]; photoScore = verticalVotes[4]; vVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(verticalVotes[4], 10); ++vVotesFound; continue; } verticalVotes = reVoteMatch2.exec(vVoteLine); if (verticalVotes) { photoNumber = verticalVotes[1]; photoScore = verticalVotes[2]; vVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(photoScore, 10); ++vVotesFound; continue; } verticalVotes = reVoteMatch1.exec(vVoteLine); if (verticalVotes) { photoNumber = verticalVotes[1]; vVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = 0; ++vVotesFound; continue; } // thisvotes is still null => normal comment } //GM_log(vVotesArray); if (vVoided) { var voidedVote = new UCPVote({ poster: poster, voteText: vVoteLines, votesArray: vVotesArray, node: challengeEntry, ucpThread: ucpThread }); voidedVote.isVoid = function () { return true; }; voteArray.include(voidedVote); } else { if (vVotesFound <= 1) { // voting for one photo makes no sense commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } else { var vote = new UCPVote({ chlgname: challengeConfig.reName(), poster: poster, voteText: vVoteLines, votesArray: vVotesArray, node: challengeEntry, ucpThread: ucpThread }); if (exampleVote) { vote.isExampleVote = function (a,b) { return true; }; }; voteArray.include(vote); } } break; case "VERTICAL-WEIGHTED": var vwVotesArray = []; var vwVoteLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? // voteLines is an array: // #01- 3+1 (4) // #02- 3 (3) // .. // others use 1: var reVwVoteMatch1 = /(\d{1,2})#/; // 01- var reVwVoteMatch2 = /(\d{1,2})#(\d+)/; // '01-1' var reVwVoteMatch3 = /(\d{1,2})#(\d+)([^0-9azA-Z](\d+))+/; // '01-1+2' && '01- 1+2+3' var vwVotesFound = 0; var vwVoided = false; for (var j = 0, vwVoteLinesLength = vwVoteLines.length; j < vwVoteLinesLength; ++j) { var vwVoteLine = vwVoteLines[j]; // remove any leading spaces vwVoteLine = vwVoteLine.replace(/^\s*/, ''); // ignore lines that are comment text if (vwVoteLine.match(/^\s*[a-zA-Z\s\.]{6}/)) { // 6? arbitrary continue; } // first seperate the photo number from the votes, in case it was seperated with a space var reVwReplaceMatch = /#(\d{1,2})/; vwVoteLine = vwVoteLine.replace(reVReplaceMatch, "$1"); // remove leading # reVwReplaceMatch = /(\d{1,2})([\-:+ ]+|\.{2,3})/; vwVoteLine = vwVoteLine.replace(reVwReplaceMatch, "$1#"); // insert # after photonumber // remove spaces vwVoteLine = vwVoteLine.replace(/\s/g, ''); // remove any character before first vote in voteLine 00#-1+2 vwVoteLine = vwVoteLine.replace(/(\d{1,2}#)[^0-9]*/, "$1"); if (vwVoteLine.match(/^\s*\d{1,2}\s*$/)) { vwVoteLine = vwVoteLine.replace(/^s*(\d{1,2})\s*$/, "$1#0"); } if (vwVoteLine.match(/void/i) && !vwVoteLine.match(/^\d/)) { // Skip replies where the vote has been voided vwVoided = true; break; } var matchedString, photoNumber, photoScore; var verticalWVotes = reVwVoteMatch3.exec(vwVoteLine); if (verticalVotes) { photoNumber = verticalWVotes[1]; photoScore = verticalWVotes[4]; vwVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(verticalWVotes[4], 10); ++vwVotesFound; continue; } verticalWVotes = reVwVoteMatch2.exec(vwVoteLine); if (verticalWVotes) { photoNumber = verticalWVotes[1]; photoScore = verticalWVotes[2]; vwVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(photoScore, 10); ++vwVotesFound; continue; } verticalWVotes = reVwVoteMatch1.exec(vwVoteLine); if (verticalWVotes) { photoNumber = verticalWVotes[1]; vwVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = 0; ++vwVotesFound; continue; } // thisvotes is still null => normal comment } //GM_log(vwVotesArray); if (vwVoided) { var voidedWVote = new UCPVote({ poster: poster, voteText: vwVoteLines, votesArray: vwVotesArray, node: challengeEntry, ucpThread: ucpThread }); voidedWVote.isVoid = function () { return true; }; voteArray.include(voidedWVote); } else { if (vwVotesFound <= 1) { // voting for one photo makes no sense commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } else { var vote = new UCPVote({ chlgname: challengeConfig.reName(), poster: poster, voteText: vwVoteLines, votesArray: vwVotesArray, node: challengeEntry, ucpThread: ucpThread }); if (exampleVote) { vote.isExampleVote = function (a,b) { return true; }; }; vote.valid = function () { return true; }; voteArray.include(vote); } } break; case "RATE-PHOTO": var rVotesArray = []; var rVoteLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? // voteLines is an array (olho no lance): // photo 01: 7 pontos // photo 02: 4 pontos // .. // or (20 temas fotograficos) // 3 puntos: #12 // 2 puntos: #2 var reVoteMatch1 = /(?:photo|foto)?\s*(\d+)[^\d]*(\d+)\s*po(?:i)?nt/i; var reVoteMatch2 = /(\d+)\s*(?:punto|pont|point|pto)[^\d]*(\d+)/i; var rVotesFound = 0; rVoteLines.forEach(function (rVoteLine) { // remove any leading spaces rVoteLine = rVoteLine.replace(/^\s*/, ''); if (rVoteLine.match(/void/i)) { // Skip replies where the vote has been voided return; } var matchedString, photoNumber, photoScore; var rateVotes = reVoteMatch1.exec(rVoteLine); if (rateVotes) { photoNumber = rateVotes[1]; photoScore = rateVotes[2]; rVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(photoScore, 10); ++rVotesFound; return; } rateVotes = reVoteMatch2.exec(rVoteLine); if (rateVotes) { photoScore = rateVotes[1]; photoNumber = rateVotes[2]; rVotesArray[parseInt(photoNumber.replace(/^0/, ''), 10)] = parseInt(photoScore, 10); ++rVotesFound; } // thisvotes is still null => normal comment }); //my_log(vVotesArray); if (rVotesFound <= 0) { commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } else { var vote = new UCPVote({ chlgname: challengeConfig.chlgname, poster: poster, voteText: rVoteLines, votesArray: rVotesArray, node: challengeEntry, ucpThread: ucpThread }); if (exampleVote) { vote.isExampleVote = function (a,b) { return true; }; } voteArray.include(vote); } break; case "HORIZONTAL": var neededPhotos = challengeConfig.neededPhotos(); if (neededPhotos < 1 || neededPhotos === 65535) { neededPhotos = photoArray.length; } var createdHVote = ucpCreateHorizontalVote( false, exampleVote, ucpThread, poster, challengeEntry, replytxt); if (createdHVote) { voteArray.include(createdHVote); } else { //GM_log(challengeConfig.options.reName + ": found another comment from user " + poster.username + ": " + replytxt); var vote = new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread }); if (exampleVote) { vote.isExampleVote = function (a,b) { return true; }; } commentArray.include(vote); } break; case "MEETANDGREET": commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); break; default: //case "PIC-*": if (/PIC-/.test(challengeConfig.scoreType())) { // PIC-V-n: pic n photos, score vertically // PIC-H-n: pic n photos, score horizontally // PIC-P-1: give a point to the player var picXmatch = /PIC-([HVP])-(\d+)/.exec(challengeConfig.scoreType()); var picX = undefined; var picOrientation = undefined; if (picXmatch && picXmatch.length > 2) { picX = parseInt(picXmatch[2], 10); } if (picXmatch && picXmatch.length > 1) { picOrientation = picXmatch[1]; } if (picOrientation === 'H') { var createdPicHVote = ucpCreateHorizontalVote(true, exampleVote, ucpThread, poster, challengeEntry, replytxt); if (createdPicHVote) { voteArray.include(createdPicHVote); } else { //GM_log(challengeConfig.options.reName + ": found another comment from user " + //poster.username + ": " + replytxt); commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } } else if (picOrientation === 'V') { // scores are a single number, on separate lines var pvVotesArray = []; var pvVoteLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? var pVotesFound = 0; var possibleExampleVote = true; // BUG: http://www.flickr.com/groups/the-storybook-challenge-group/discuss/72157625609019459/ // 1. pic-v-3 with vote 2 3 1, is not an example vote // 2. the second vote is considered an example vote; should never happen for (var k = 0, pvVoteLinesLength = pvVoteLines.length; k < pvVoteLinesLength; ++k) { var pvVoteLine = pvVoteLines[k]; // remove any leading spaces pvVoteLine = pvVoteLine.replace(/^\s*/, '').replace(/#\s+(\d+)/, "$1"); pvVoteLine = pvVoteLine.replace(/^foto[^\d]*|^photo[^\d]*/i, ''); pvVoteLine = pvVoteLine.replace(/^n°[^\d]*/i, ''); // ignore lines that are comment text if (pvVoteLine.match(/^\s*[a-zA-Z\s\.\,]{6}/)) { // 6? arbitrary continue; } var reReplaceMatch = /#(\d{1,2})/; pvVoteLine = pvVoteLine.replace(reReplaceMatch, "$1"); // remove leading # var picVoteMatch = /^\s*(\d+)/.exec(pvVoteLine); if (picVoteMatch) { var voteIdx = parseInt(picVoteMatch[1].replace(/^0(\d+)/, '$1'), 10); if (voteIdx < 256) { // if someone enters a 'summary' of the scores (2302042141) => oom pvVotesArray[voteIdx] = 1; ++pVotesFound; continue; } } } if (picX === undefined || pVotesFound <= 0) { commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } else { // picX defined if (pVotesFound < picX) { commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } else { var picVVote = new UCPVote({ chlgname: challengeConfig.reName(), poster: poster, voteText: pvVoteLines, votesArray: pvVotesArray, node: challengeEntry, ucpThread: ucpThread }); // also catches rules: // 1) ... // 2) ... if (!exampleVote) { var picExampleVote = picX > 1; // assume an example vote, only if voting for multiple // voting for photo 1 results in example vote otherwise for (var picIdx = 1, picLen = pvVotesArray.length; picIdx < picLen; ++picIdx) { // skip index 0: 1-based voting if (!pvVotesArray[picIdx] || isNaN(pvVotesArray[picIdx])) { picExampleVote = false; break; } } } if (picExampleVote || exampleVote) { picVVote.isExampleVote = function (scoresAdded, neededScore) { return true; }; // override default behaviour } voteArray.include(picVVote); } } } else if (picOrientation === 'P') { // used in MatchPoint (http://www.flickr.com/groups/matchpoint/discuss/) var ppVoteLines = replytxt.split(/<br>\s*/); // source code shows '<br />', DOM uses <br>? var score, player; var ppVote = undefined; for (var l = 0, ppVoteLinesLength = ppVoteLines.length; l < ppVoteLinesLength && !ppVote; ++l) { var ppVoteLine = ppVoteLines[l]; // (remove buddyicons first: contains @) ppVoteLine = ppVoteLine.replace(/<img[^>]+>/g, ''); // ignore lines that are comment text if (!ppVoteLine.match(/@|#|p(oi)?nt/i)) { continue; } // remove any leading spaces ppVoteLine = ppVoteLine.replace(/^\s*/, ''); // remove html, which does not fit in a username ppVoteLine = ppVoteLine.replace(/[@#](\s*[^<]*)/, '@ $1'); var picPVoteMatch = /(\d+)[^\d]*[@#]\s*([^<]*)/.exec(ppVoteLine); if (!picPVoteMatch) { picPVoteMatch = ppVoteLine.match(/(?:point|pnt)[^\d]*(\d+)[^@#]*[@#]\s*([^<]*)/i); } var ppVotesArray = []; //[challengeConfig.options.neededPhotos + 1]; if (picPVoteMatch) { score = picPVoteMatch[1]; player = picPVoteMatch[2]; // cleanup player player = player.replace(/<[^>]*>/g, '').replace(/\s*$/, ''); player = player.replace(/ /g, ' '); // remove insignificant characters at the end of the player entry player = player.replace(/(?:\.|\!|\s)+$/, ''); // compensate for spaces, and underscores in usernames, or the lack of them player = player.replace(/(?:_|\s)+/g, ''); for (var ppPhotoIdx = 0, ppPhotoLen = photoArray.length; ppPhotoIdx < ppPhotoLen; ++ppPhotoIdx) { var photo = photoArray[ppPhotoIdx]; if (!(photo instanceof UCPCompetitor)) { continue; } var challenger = photo.poster().username.replace(/(?:_|\s)+/g, ''); // voters make shortcuts for usernames, and don't care for case // they also misspell Mustela, and anglerove if (photo && ( (challenger.match(/Mustela.Nivalis/) && player.match(/Must(e|a)ll?a/i)) || (challenger.match(/anglerove/) && player.match(/angelrove/i)) )) { ppVotesArray[ppPhotoIdx + 1] = 1; //parseInt(score, 10); ppVote = new UCPVote({ chlgname: challengeConfig.reName(), poster: poster, voteText: ppVoteLines, votesArray: ppVotesArray, node: challengeEntry, ucpThread: ucpThread }); continue; } if (photo && challenger.toLowerCase().match(player.toLowerCase())) { ppVotesArray[ppPhotoIdx + 1] = 1; // parseInt(score, 10); ppVote = new UCPVote({ chlgname: challengeConfig.reName(), poster: poster, voteText: ppVoteLines, votesArray: ppVotesArray, node: challengeEntry, ucpThread: ucpThread }); } } } } if (ppVote) { voteArray.include(ppVote); } else { commentArray.include(new UCPVoteComment({ poster: poster, node: challengeEntry, ucpThread: ucpThread })); } } } } } }); // each entry return { votes: voteArray, comments: commentArray, photos: photoArray }; } function ucpProcessVotes(ucpThread, playername, votes, photosposted) { var doubleVotesHash = new Hash(); var doublePlayerHash = new Hash(); var cummulativeScore = new UCPVote({ chlgname: null, poster: "flickr", voteText: "none", votesArray: [], commentAnchor: null, ucpThread: ucpThread }); var previousVote = null; $each(votes, function (vote, j) { if (previousVote === null && vote.isExampleVote(ucpThread.challengeDefinition().scoresAdded(), ucpThread.challengeDefinition().neededScore())) { vote.addMessage("found an example vote"); vote.printStatus(); return; } if (vote.isVoid()) { vote.addMessage("found a voided vote"); vote.printStatus(); return; } vote.addMessage( "<b>" + ( vote.poster().username === playername ? "you" : vote.poster().username ) + ( vote.poster().admin ? " (admin/mod)" : "" ) + "</b> voted"); if (doubleVotesHash.has(vote.poster().userid)) { // this member already voted! vote.addError("voted more than once"); //ucpThread.addVotingError("'" + vote.poster().username + "' voted more than once"); } else { doubleVotesHash.set(vote.poster().userid, vote.poster().username); // whatever } if (vote.poster().username === playername) { ucpThread.updateStatus("voter"); } if (ucpThread.challengeDefinition().scoreType().match(/PIC-[HV]/)) { vote.addMessage(vote.showPicVotes()); cummulativeScore.add(vote); } else if (ucpThread.challengeDefinition().scoreType().match(/RATE-PHOTO/)) { // DO NOTHING } else if (ucpThread.challengeDefinition().scoreType().match(/VERTICAL-WEIGHTED/)) { // DO NOTHING } else { // HORIZONTAL or VERTICAL or PIC-P if (ucpThread.challengeDefinition().scoreType().match(/PIC-P-/)) { cummulativeScore.add(vote); } else { cummulativeScore = vote; } if (ucpThread.finished(cummulativeScore)) { ucpThread.updateStatus("finished"); } var currentValidVoting = true; if (previousVote) { currentValidVoting = vote.valid(previousVote, ucpThread.challengeDefinition().scoresAdded()); // photos.length, debug); } else { vote.calculateVotedFor(ucpThread.challengeDefinition().scoresAdded()); // for the first vote } if (vote.votedFor > 0) { vote.addMessage("for photo " + vote.votedFor); } var votingResult = ucpThread.challengeDefinition().scoreType().match(/PIC-P/) ? cummulativeScore.showPicResult() : vote.showVotes(); if (!currentValidVoting && !ucpThread.challengeDefinition().scoreType().match(/PIC-P/)) { ucpThread.addVotingError("'" + vote.poster().username + "' " + vote.error()); if (!ucpThread.validVoting()) { // already in error from previous vote vote.messages().each(function (message) { message.type = 'warning'; }); } } vote.addMessage(votingResult); } if (ucpThread.challengeDefinition().playerVoting() === "maynotvote") { if (doublePlayerHash.has(vote.poster().userid)) { ucpThread.addVotingError( "'" + vote.poster().username + "' voted in a challenge he/she plays in"); vote.addError("in a challenge he/she plays in"); } } vote.printStatus(); if (ucpThread.challengeDefinition().scoreType().match(/PIC-P/)) { previousVote = cummulativeScore; previousVote.options.poster = vote.options.poster; } else { previousVote = vote; } /* TODO: if (vote.poster().username === playername && !vote.hasError()) { vote.setProvideDiscussLink(true); } */ }); previousVote = null; //overwrite some base statusses if challenge is in voting. if (ucpThread.challengeName().match(groupConfig.states().closed)) { // should never happen // GM_log("overwriting: " + ucpThread.challengeName() + " matches " + groupConfig.states().closed); ucpThread.updateStatus("closed"); } if (ucpThread.finished(cummulativeScore)) { ucpThread.updateStatus("finished"); } GM_log("checkStatus("+photosposted+")"); ucpThread.checkStatus(photosposted); // TODO: if vote has just been cast (url/#comment1234..), and is ok, automatically open Discuss page if (ucpThread.challengeStatus() === "Finished" || ucpThread.challengeStatus() === "PlayerFinished" || ucpThread.challengeStatus() === "PlayerVoted" || ucpThread.challengeStatus() === "Voted" || ucpThread.challengeStatus() === "Closed" || ucpThread.challengeStatus() === "Player") { if (ucpThread.challengeDefinition().scoreType().match(/PIC-[HVP]/)) { ucpThread.setScoreSummary(cummulativeScore.showPicResult()); } else if (ucpThread.challengeDefinition().scoreType().match(/RATE-PHOTO|VERTICAL-WEIGHTED/)) { // do nothing } else { ucpThread.setScoreSummary(cummulativeScore.showVotes()); } } //let's go and change the update status on screen } // end former commonLibraryNG // defaults var playername = GM_getLoggedInUser(); //var playername = "Little_Debbie"; DEBUG var playernumber = 0; // keep it global! passing it along as a member of groupConfig seems not to work var groupConfig; var groupPreferences; var ucpLanguage; var reGroupnameMatch = /.*flickr.com\/groups\/([^\/.]*)\//; var groupname = reGroupnameMatch.exec(document.location.href)[1]; // needed for version check var userNsid; var adminOrMod = false; var debug = false; var ucpGroupConfigReader; var ucpLanguageConfigReader; var topicListingTable; function initialize() { ucpGroupConfigReader = new UCPGroupConfigReader(); try { groupConfig = ucpGroupConfigReader.createGroupConfig(); } catch (e) { GM_log("unsupported group; aborting (" + e + ")"); return false; } if (!$chk(groupConfig)) { GM_log("unsupported group; aborting"); return false; } if (!$chk(playername) || playername === "") { GM_log("Sign in to your Flickr account if you want to take part in challenges in this group"); return false; } ucpAddCPheader(); groupPreferences = new UCPGroupPreferences({groupConfig: groupConfig}); ucpLanguageConfigReader = new UCPLanguageConfigReader(); ucpLanguage = ucpLanguageConfigReader.createLanguage(groupPreferences.language(), groupConfig.hasLegacyLabels() && groupPreferences.useLegacyLabels() ? groupConfig.legacyLabels() : undefined, groupConfig.languageOverrides()); userNsid = GM_getGlobalNsid(); adminOrMod = groupConfig.isGroupAdministratorOrModerator(userNsid); /*if (adminOrMod && $chk(groupPreferences.ucpStyle())) { groupPreferences.setStyle({ ucpStyle: groupPreferences.ucpStyle(), ucpStyleRemoveNew: groupPreferences.ucpRemoveNew(), ucpStyleFirstColumn: groupPreferences.ucpFirstColumn(), ucpStyleSort: false // TODO? move the info row with the challenge row! }); } */ try { topicListingTable = $('Main').getElement('table.TopicListing'); } catch (e) { } return true; } function applyPreferences() { var groupname = groupConfig.groupname(); var somethingChanged = false; // javascript evaluates from left to right! somethingChanged = groupPreferences.setStyle({ ucpStyle: $('ucpstyleRadio').checked, ucpStyleRemoveNew: $('ucpStyleRemoveNew').checked, ucpStyleFirstColumn: $('ucpStyleFirstColumn').checked, ucpStyleSort: $('ucpStyleSort').checked }) || somethingChanged; somethingChanged = groupPreferences.setLanguage($('languageSelect').value) || somethingChanged; if (groupConfig.hasLegacyLabels() && !groupConfig.mandatoryGroupLabels()) { somethingChanged = groupPreferences.setUseLegacyLabels($('legacyCheckId').checked) || somethingChanged; } if (somethingChanged) { window.location.reload(); } else { var configDialog = $("UCPNGConfigDialogDiv"); if (configDialog) { configDialog.destroy(); } } } // debug // new groups: // http://www.flickr.com/groups/lamanoamiga/discuss/ // http://www.flickr.com/groups/772776@N21/ // Nature's Pot-of-Gold Challenge Rooms // http://www.flickr.com/groups/face-off/ // http://www.flickr.com/groups/26485789@N00/ // http://www.flickr.com/groups/like_it_or_not_challenges/ // http://www.flickr.com/groups/grandes_temas/ // http://www.flickr.com/groups/vaiencarar/ // http://www.flickr.com/groups/26485789@N00/ (you versus the best) function toggleGroupList() { var groupListDialog = $("UCPNGSupportedGroupListDiv"); if (groupListDialog) { groupListDialog.dispose(); } else { var table = new Element('table', { styles: { border: '0', cellPadding: '5', cellSpacing: '0' } }); var rowIndex = 0; var row, col; var groupList = ucpGroupConfigReader.groupList(); $each(groupList, function (group, groupname) { if (!$chk(group.hideFromSupportedGroups) || group.hideFromSupportedGroups !== true) { if (rowIndex % 3 === 0) { row = new Element('tr').inject(table); } else { col = new Element('td', { html: " ", styles: { borderRight: 'solid grey 1px' } }).inject(row); } rowIndex++; new Element('a', { href: "http://www.flickr.com/groups/" + groupname + "/discuss/", html: group.name }).inject(new Element('td').inject(row)); } }); new Element('button', { type: 'submit', html: 'Close', 'class': 'CancelButt', events: { click: toggleGroupList } }).inject(new Element('td', { colSpan: '8', align: 'right' }).inject(new Element('tr').inject(table))); var dialogDiv = new Element('div', { 'id': "UCPNGSupportedGroupListDiv", styles: ucpDialogStyle }).adopt(table); $('UCheckPlayNGPreferences').getParent().adopt(dialogDiv); } } function togglePreferencesDialog() { var configDialog = $("UCPNGConfigDialogDiv"); if (configDialog) { configDialog.destroy(); } else { new Element('div', { id: "UCPNGConfigDialogDiv", styles: ucpDialogStyle }).inject($("UCheckPlayNGPreferences").getParent()).adopt( table = new Element('table', { border: '0', cellPadding: '5', cellSpacing: '0' })); // title new Element('tr').adopt(new Element('td', { colspan: '3', width: '100%' }).adopt( new Element('table', { border: '0', cellPadding: '0', cellSpacing: '0' }).adopt( new Element('tr').adopt( new Element('td', { noWrap: 'nowrap', vAling: 'top', styles: { fontWeight: 'bold' }, html: "Preferences for this group " }), new Element('td', { width: '100%' }), new Element('td', { align: 'right' }).adopt( new Element('a', { target: '_blank', html: 'help', href: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623600133810/#comment72157623475618601', styles: { cursor: 'help' } })) ) ))).inject(table); // style new Element('tr').inject(table).adopt( new Element('th', { colSpan: '3', noWrap: 'nowrap', vAling: 'top', html: "Display style:", styles: { background: '#CFCFCF' } })); new Element('tr').inject(table).adopt( new Element('td', { noWrap: 'nowrap', vAling: 'top', }).adopt( new Element('input', { type: 'radio', name: 'displaystyle', value: 'flickr', id: 'flickrstyleRadio', checked: !groupPreferences.ucpStyle(), events: { click: function () { $('ucpStyleRemoveNew').disabled = true; $('ucpStyleFirstColumn').disabled = true; $('ucpStyleSort').disabled = true; $('ucpStyleRemoveNew').getNext().style.color = 'grey'; $('ucpStyleFirstColumn').getNext().style.color = 'grey'; $('ucpStyleSort').getNext().style.color = 'grey'; } } }), new Element('label', { 'for': "flickrstyleRadio", html: "Flickr style" })), new Element('td', { styles: { fontStyle: 'italic' }, html: "The default Flickr style" })); new Element('tr').inject(table).adopt( new Element('td', { noWrap: 'nowrap', vAling: 'top' }).adopt( new Element('input', { type: 'radio', name: 'displaystyle', value: 'ucp', id: 'ucpstyleRadio', checked: groupPreferences.ucpStyle(), events: { click: function () { $('ucpStyleRemoveNew').disabled = false; $('ucpStyleFirstColumn').disabled = false; $('ucpStyleSort').disabled = false; $('ucpStyleRemoveNew').getNext().style.color = 'black'; $('ucpStyleFirstColumn').getNext().style.color = 'black'; $('ucpStyleSort').getNext().style.color = 'black'; } } }), new Element('label', { 'for': "ucpstyleRadio", html: "UCP style" })), new Element('td', { styles: { fontStyle: 'italic' }, html: "Unified CheckPlay style:" }).adopt( new Element('ul').adopt( new Element('li', { html: "fixes the width of the author column" }), new Element('li', { html: "prevents wrapping in the author column" }), new Element('li', { html: "makes the 'Latest Post' column smaller" })), new Element('br'), new Element('input', { type: 'checkbox', id: 'ucpStyleRemoveNew', checked: groupPreferences.ucpStyle() ? groupPreferences.ucpRemoveNew() : true, disabled: !groupPreferences.ucpStyle() }), new Element('label', { 'for': "ucpStyleRemoveNew", html: "remove 'NEW' labels", styles: { color: !groupPreferences.ucpStyle() ? 'grey' : '' } }), new Element('br'), new Element('input', { type: 'checkbox', id: 'ucpStyleFirstColumn', checked: groupPreferences.ucpStyle() ? groupPreferences.ucpFirstColumn() : true, disabled: !groupPreferences.ucpStyle() }), new Element('label', { 'for': "ucpStyleFirstColumn", html: "move the status column to the front" + " (Warning: this option conflicts with existing " + "CheckPlay scripts that are installed <u>after</u> UCheckPlay)", styles: { color: !groupPreferences.ucpStyle() ? 'grey' : '' } }), new Element('br'), new Element('input', { type: 'checkbox', id: 'ucpStyleSort', checked: /*adminOrMod ? false :*/ groupPreferences.ucpStyle() ? groupPreferences.ucpSort() : true, disabled: /*adminOrMod ||*/ !groupPreferences.ucpStyle() }), new Element('label', { 'for': "ucpStyleSort", html: "move 'VOTE' challenges to the top, and move 'VOTED' and 'FINISHED' challenges to the bottom", styles: { color: /*adminOrMod ||*/ !groupPreferences.ucpStyle() ? 'grey' : '' } }) ) ); // language new Element('tr').inject(table).adopt( new Element('th', { colSpan: '3', noWrap: 'nowrap', vAling: 'top', html: "Language:", styles: { background: '#CFCFCF' } })); new Element('tr').inject(table).adopt( new Element('td').adopt( languageSelect = new Element('select', { styles: { background: '#F0F0F0' }, title: 'UCP language: select your preferred language for UCP feedback', id: 'languageSelect' })), new Element('td', { styles: { fontStyle: 'italic' }, html: "Language for titles, and labels." }) ); var languages = ucpLanguageConfigReader.getLanguageList(); $each(languages, function (definition, language) { new Element('option', { value: language, selected: groupPreferences.language() === language, disabled: definition === undefined, title: (definition === undefined ? 'Not implemented yet. Maybe you can help us out?' : language), html: language }).inject($('languageSelect')); } ); // legacy labels if (groupConfig.hasLegacyLabels() && !groupConfig.mandatoryGroupLabels()) { new Element('tr').inject(table).adopt( new Element('th', { colSpan: '3', html: "Legacy status icons:", styles: { background: '#CFCFCF' } })); new Element('tr').inject(table).adopt( new Element('td', { noWrap: 'nowrap' }).adopt( new Element('input', { type: 'checkbox', name: 'legacyicons', id: 'legacyCheckId', checked: groupPreferences.useLegacyLabels() }), new Element('label', { 'for': 'legacyCheckId', html: 'Use legacy status icons' })), new Element('td', { style: { fontStyle: 'italic' }, html: "Use the same status icons as the legacy CheckPlay script for this group" })); } new Element('tr').inject(table).adopt( new Element('td', { colSpan: '3', align: 'right' }).adopt( new Element('button', { type: 'submit', 'class': 'Butt', html: 'OK', events: { click: applyPreferences } }), document.createTextNode(" "), new Element('button', { type: 'submit', 'class': 'DeleteButt', html: 'Cancel', events: { click: togglePreferencesDialog } }) )); // group definitions new Element('tr').inject(table).adopt( new Element('th', { colSpan: '3', noWrap: 'nowrap', vAling: 'top', html: "Definitions:", styles: { background: '#CFCFCF' } })); new Element('tr').inject(table).adopt( new Element('td', { noWrap: 'nowrap' }).adopt( new Element('input', { type: 'checkbox', id: 'languageReload' }), new Element('label', { 'for': 'languageReload', html: 'language definitions' }) ), new Element('td', { styles: { fontStyle: 'italic' }, html: "The definitions for the language translations are updated once a month. With this option, you " + "can update them now." }) ); new Element('tr').inject(table).adopt( new Element('td', { noWrap: 'nowrap' }).adopt( new Element('input', { type: 'checkbox', id: 'groupReload' }), new Element('label', { 'for': 'groupReload', html: 'challenge definitions' }) ), new Element('td', { styles: { fontStyle: 'italic' }, html: "The definitions for the challenges are updated once a week. With this option, you " + "can update them now." }) ); new Element('tr').inject(table).adopt( new Element('td').adopt( new Element('button', { type: 'submit', 'class': 'Butt', html: 'update', id: 'definitionsButton', events: { click: function (evt) { // clean up some previous update actions $('languageReload').getParent('td').getElements('img').destroy(); $('groupReload').getParent('td').getElements('img').destroy(); if ($('languageReload').checked) { GM_log("need to reload language defs"); new Element('img', { src: updatingIcon }).inject($('languageReload').getParent('td')); ucpLanguageConfigReader.checkForUpdates(groupPreferences.language(), true, function (result) { var uploadImg = $('languageReload').getParent('td').getElement('img'); uploadImg.set('src', result.stat == 'ok' ? defaultCheckIconSmall : errorIcon); uploadImg.set('title', result.stat == 'ok' ? 'updated' : result.error); }); } if ($('groupReload').checked) { GM_log("need to reload group defs"); new Element('img', { src: updatingIcon }).inject($('groupReload').getParent('td')); ucpGroupConfigReader.checkForUpdates(groupname, true, function (result) { var uploadImg = $('groupReload').getParent('td').getElement('img'); uploadImg.set('src', result.stat == 'ok' ? defaultCheckIconSmall : errorIcon); uploadImg.set('title', result.stat == 'ok' ? 'updated' : result.error); }); } } } }) ) ); } } // ----- function cleanupUCPvariables(groupname) { var keyValues = GM_listValues(); for (var keyIdx = 0, valLen = keyValues.length; keyIdx < valLen; ++keyIdx) { var key = keyValues[keyIdx]; if (!$chk(groupname)) { if (key.match(/UCP./) && // for versions prior to 0.5.10 !key.match(/UCP.language/) && !key.match(/UCP.ucpStyle/) && !key.match(/UCP.ucpStyle.removeNew/) && !key.match(/UCP.ucpStyle.firstColumn/) && !key.match(/UCP.ucpStyle.sort/) && !key.match(/UCP.useLegacyIcons/) && !key.match(/UCP.lastVersionCheckTime/)) { GM_deleteValue(key); } } else { // only for the given group if (key.match(new RegExp("UCP." + groupname))) { GM_deleteValue(key); } } } } var UCPMarkFunction = new Class({ Implements: [Options], options: { markAnchor: undefined, imgNode: undefined, marked: false }, initialize: function (options) { this.setOptions(options); }, handleEvent: function (e) { if (this.options.marked) { this.options.marked = false; this.options.imgNode.set('style', 'border: 0px; max-width: 75; max-height: 75'); this.options.markAnchor.set('html', this.options.markAnchor.get('html').replace("marked", "mark")); this.options.markAnchor.style.background = "yellow"; this.options.markAnchor.title = "click to mark"; } else { this.options.marked = true; this.options.imgNode.set('style', 'border: 7px solid magenta; max-width: 75; max-height: 75'); this.options.markAnchor.set('html', this.options.markAnchor.get('html').replace("mark", "marked")); this.options.markAnchor.set('style', 'background: magenta'); this.options.markAnchor.title = "click to unmark"; } } }); function showPhotoSummary(photos) { var form = $$('table.TopicReply form textarea'); if (!$chk(form) || form.length === 0) { GM_log("no form found (3)"); return; } var helpmeButton = new Element('button', { 'class': 'Butt', html: 'thumbnail view', events: { click: function () { toggleThumbnailView(photos); } } }).inject($('Main').getElement('a[name=reply]').getParent(), 'before'); var helplink = new Element('a', { target: '_blank', html: 'what is this?', href: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623600133810/#comment72157623601636570', styles: { cursor: 'help' } }).inject(helpmeButton, 'after'); } function toggleThumbnailView(photos) { if ($chk($('UCP:thumbnail_table'))) { $('UCP:thumbnail_table').dispose(); $$('table.helpmeanchor').dispose(); $$('a[name=helpme]').dispose(); } else { var main = $$("body div#Main table")[0]; var photoTableAnchor = new Element('a', { name: "helpme" }); photoTableAnchor.inject(main, 'after'); var photoTable = new Element('table', { id: "UCP:thumbnail_table" }); photoTable.inject(photoTableAnchor, 'after'); photoTable.adopt( new Element('tr').adopt( new Element('th', { colSpan: 5, styles: { color: "brown" }, html: "Please view the photos in medium size first.<br/>" + "You will find a 'mark' link below every photo. " + "Click the link to mark the photo's thumbnail below.<br/>" + "This should make it easier to narrow down your selection." }), new Element('th', { colSpan: 5, styles: { color: 'brown', 'text-align': 'right' }, html: "Double-check the numbers please!<br/>" + "!! And don't forget to enter your vote !!" }) ) ); var markAnchorList = []; photos.each(function (competitor, photoIdx) { if (photoIdx % 9 === 0) { photoTable.adopt(row = new Element('tr')); } // create a named anchor to link back to var namedAnchor, photoId; if (competitor.photo().getParent().get('href')) { namedAnchor = competitor.photo().getParent(); try { photoId = competitor.photo().src.match(/http:\/\/.*flickr.com\/\d+\/(\d+)_.*/)[1]; if (photoId) { namedAnchor.name = photoId; } } catch (e) { } } var cell; row.adopt(cell = new Element('td', { html: photoIdx + 1 })); if (namedAnchor && photoId) { var photoAnchor; cell.adopt(photoAnchor = new Element('a', { href: '#' + photoId })); // trick the rest of the script cell = photoAnchor; } var thumb; cell.adopt(thumb = new Element('img', { src: competitor.photo().src, alt: 'UCPthumbnail', styles: { 'max-width': 75, 'max-height': 75 } })); // fetch the thumbnail version from Flickr? will not work for photos with all rights reserved // => no 'All sizes', no public thumbnail var markAnchor = { anchor: new Element('a', { html: "mark", styles: { background: "yellow", cursor: 'pointer' }, title: "click to mark", 'class': 'helpmeanchor' }), photo: competitor.photo() }; //markAnchor.anchor.style.textDecoration = "underline"; var markFunction = new UCPMarkFunction({ markAnchor: markAnchor.anchor, imgNode: thumb }); markAnchor.anchor.addEventListener('click', markFunction, false); markAnchorList.push(markAnchor); }); markAnchorList.each(function (markAnchor) { var div = new Element('div', { width: '100%', 'class': 'helpmeanchor' }).adopt( new Element('table', { 'class': 'helpmeanchor', width: '100%' }).adopt( new Element('rw').adopt( new Element('td').adopt(markAnchor.anchor), new Element('td', { align: 'right', width: '100%' }).adopt( new Element('a', { 'class': 'toThumbnails', href: '#helpme', html: 'to the thumbnails', }) ) ) ) ); if ($chk(markAnchor.photo.getParent('a'))) { div.inject(markAnchor.photo.getParent('a'), 'after'); } else { // hope for the best div.inject(markAnchor.photo, 'after'); } }); document.location.href = "#helpme"; } } function processDiscussionTopic(discussionTopic, ucpThread) { /*var threadNr = ucpThread.challengeName().match(/^\d+/); if ($chk(threadNr)) { ucpThread.startprocessing = new Date(); }*/ var debug = false; //ucpThread.challengeName().match('Winter'); var playerInThisTopic = false; var form = $(discussionTopic).getElement('table.TopicReply form textarea'); if (!$chk(form)) { // closed, or not a member form = $(discussionTopic).getElement('table.TopicReply div.Tease'); if (!$chk(form) || form.length === 0) { GM_log("no form found"); ucpThread.resetStatus(); ucpThread.setChallengeStatus("Closed"); ucpThread.store(); if (!adminOrMod) { ucpThread.printStatus(groupPreferences, ucpLanguage); } } } if ($chk(form)) { var formFeedback = new Element('small', { id: 'UCPNGFormFeedback', styles: { textDecoration: 'none' } }).inject(form, 'before'); ucpThread.setFeedbackElement(formFeedback); } else if (adminOrMod) { var closedMessage = $('Main').getElement('table p.Focus'); if ($chk(closedMessage)) { //GM_log("found the Closed message"); var formFeedback = new Element('small', { id: 'UCPNGFormFeedback', styles: { textDecoration: 'none' } }).inject(closedMessage, 'before'); ucpThread.setFeedbackElement(formFeedback); } } if (ucpThread.challengeDefinition().nonChallengeType()) { ucpThread.resetStatus(); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); return; } var page = document.location.href.match(/\/page(\d+)/); var stopProcessing = false; if (page) { page = parseInt(page[1], 10); stopProcessing = page > 1; } ucpThread.setLastLoadTime(Math.round(CPStartTime.getTime() / 1000)); var nonChallengeStatus; var challengeAnnouncement = $(discussionTopic).getElement('td.Said'); if (!$chk(challengeAnnouncement) || challengeAnnouncement.length === 0) { GM_log("no challengeAnnouncement!"); ucpThread.setChallengeStatus("ERRORPARSING"); ucpThread.addVotingError("empty topic"); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); return; } if (!stopProcessing) { if ((!$chk(form) || form.length === 0) && !ucpThread.challengeDefinition().nonChallengeType()) { GM_log("no form found (2)"); ucpThread.resetStatus(); ucpThread.setChallengeStatus("Closed"); ucpThread.store(); if (!adminOrMod) { // continue to show the result, errors, .. ucpThread.printStatus(groupPreferences, ucpLanguage); return; } } } // the announcement could contain override information: // <img src="..." alt="UCPoverride:photos:3"/> // <img src="..." alt="UCPoverride:votes:10"/> ucpThread.challengeDefinition().readChallengeDefinitionOverrides(challengeAnnouncement); // re-test !! if (ucpThread.challengeDefinition().nonChallengeType()) { ucpThread.resetStatus(); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); return; } if (ucpThread.challengeStatus() !== "Closed") { // in case of admins, continue ucpThread.setChallengeStatus("none"); } var photosposted = 0; if (!$chk(discussionTopic)) { GM_log("no discussion topic?"); ucpThread.setChallengeStatus("ERRORPARSING"); ucpThread.addVotingError("no DiscussTopic"); ucpThread.store(); return; } if (debug) GM_log("ucpThread: " + ucpThread.toString()); if (debug) GM_log("challenge: " + ucpThread.challengeDefinition().toString()); // discussionTopic contains the challenge announcement (table 1) and the replies (table 2); var replies = discussionTopic.getElements('table.TopicReply tbody tr'); if (replies.length === 0) { GM_log("no replies yet in " + ucpThread.challengeName()); ucpThread.resetStatus(); ucpThread.setChallengeStatus("none"); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); return; } // if (groupConfig.excludeReplyIndexes().contains(0)) { ucpThread.findExcludesInDOMNode(challengeAnnouncement); if (ucpThread.isExcluded(playername)) { ucpThread.updateStatus("Excluded"); } } // get all 'says:' elements var challengeEntries = discussionTopic.getElements('td.Said'); if (!challengeEntries || challengeEntries.length === 0) { // no replies ucpThread.resetStatus(); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); return; } var commentcounter = challengeEntries.length - 1; if (!stopProcessing) { // only on first page groupConfig.excludeReplyIndexes().each(function (index) { var challengeEntry = challengeEntries[index]; if (challengeEntry && index > 0) { // already done the announcement ucpThread.findExcludesInDOMNode(challengeEntry); if (ucpThread.isExcluded(playername)) { ucpThread.updateStatus("Excluded"); } } }); ucpThread.printExcludes(challengeAnnouncement); } var replyArray = ucpThread.collectVotes(challengeAnnouncement, challengeEntries); var votes = replyArray.votes; var comments = replyArray.comments; var photos = replyArray.photos; var doubleVotesHash = new Hash(); var doublePlayerHash = new Hash(); photosposted = photos.length; $each(photos, function (photo) { var photoposter = photo.poster().username; if (photoposter === playername) { ucpThread.updateStatus("photoposter"); if (ucpThread.challengeDefinition().countsToLimit() && !playerInThisTopic) { ++playernumber; playerInThisTopic = true; } } if (ucpThread.challengeDefinition().limitPerPlayer() > 0) { if (doublePlayerHash.has(photo.poster().userid)) { // this member has more than one photo entered var entries = doublePlayerHash.get(photo.poster().userid) + 1; if (entries > ucpThread.challengeDefinition().limitPerPlayer()) { if (!photo.poster().admin) { ucpThread.addVotingError("'" + photo.poster().username + "' entered more than " + ucpThread.challengeDefinition().limitPerPlayer() + " photo(s)"); } photo.addError("(max. photos allowed: " + ucpThread.challengeDefinition().limitPerPlayer() + ")"); } doublePlayerHash.set(photo.poster().userid, entries); } else { doublePlayerHash.set(photo.poster().userid, 1); } } //GM_log("comparing '"+excludedPlayer+"' with '"+vote.username+"'"); if (ucpThread.excludedPlayers().contains(photo.poster().username)) { ucpThread.addVotingError("'" + photo.poster().username + "' was excluded, but entered anyway"); photo.addError(photo.poster().username + " was excluded, but entered anyway"); } if (!photo.checkForValidPoster()) { if (!photo.poster().admin) { ucpThread.addVotingError("'" + photo.poster().username + "' posted a photo of someone else"); photo.addError("posted a photo of someone else"); } else { photo.addWarning("posted a photo of someone else"); } } photo.printStatus(); }); // photo loop // non-vote comments $each(comments, function (comment) { comment.ucpThread = ucpThread; if (ucpThread.challengeDefinition().scoreType() === "MEETANDGREET") { if (comment.poster().username === playername) { ucpThread.updateStatus("voter"); } } comment.printStatus(); }); // votes var cummulativeScore = new UCPVote({ chlgname: null, poster: "flickr", voteText: "none", votesArray: [], commentAnchor: null, ucpThread: ucpThread }); var previousVote = null; $each(votes, function (vote, j) { if (previousVote === null && vote.isExampleVote(ucpThread.challengeDefinition().scoresAdded(), ucpThread.challengeDefinition().neededScore())) { vote.addMessage("found an example vote"); vote.printStatus(); return; } if (vote.isVoid()) { vote.addMessage("found a voided vote"); vote.printStatus(); return; } vote.addMessage( "<b>" + ( vote.poster().username === playername ? "you" : vote.poster().username ) + ( vote.poster().admin ? " (admin/mod)" : "" ) + "</b> voted"); if (doubleVotesHash.has(vote.poster().userid)) { // this member already voted! vote.addError("voted more than once"); ucpThread.addVotingError("'" + vote.poster().username + "' voted more than once"); } else { doubleVotesHash.set(vote.poster().userid, vote.poster().username); // whatever } if (vote.poster().username === playername) { ucpThread.updateStatus("voter"); } if (ucpThread.challengeDefinition().scoreType().match(/PIC-[HV]/)) { vote.addMessage(vote.showPicVotes()); cummulativeScore.add(vote); } else if (ucpThread.challengeDefinition().scoreType().match(/RATE-PHOTO/)) { // DO NOTHING } else if (ucpThread.challengeDefinition().scoreType().match(/VERTICAL-WEIGHTED/)) { // DO NOTHING } else { // HORIZONTAL or VERTICAL or PIC-P if (ucpThread.challengeDefinition().scoreType().match(/PIC-P-/)) { cummulativeScore.add(vote); } else { cummulativeScore = vote; } if (ucpThread.finished(cummulativeScore)) { ucpThread.updateStatus("finished"); } var currentValidVoting = true; if (previousVote) { currentValidVoting = vote.valid(previousVote, ucpThread.challengeDefinition().scoresAdded(), photos.length, debug); } else { vote.calculateVotedFor(ucpThread.challengeDefinition().scoresAdded()); // for the first vote } if (vote.votedFor > 0) { vote.addMessage("for photo " + vote.votedFor); } var votingResult = ucpThread.challengeDefinition().scoreType().match(/PIC-P/) ? cummulativeScore.showPicResult() : vote.showVotes(); if (!currentValidVoting && !ucpThread.challengeDefinition().scoreType().match(/PIC-P/)) { ucpThread.addVotingError("'" + vote.poster().username + "' " + vote.error()); if (!ucpThread.validVoting()) { // already in error from previous vote vote.messages().each(function (message) { message.type = 'warning'; }); } } vote.addMessage(votingResult); } if (ucpThread.challengeDefinition().playerVoting() === "maynotvote") { if (doublePlayerHash.has(vote.poster().userid)) { ucpThread.addVotingError( "'" + vote.poster().username + "' voted in a challenge he/she plays in"); vote.addError("in a challenge he/she plays in"); } } if (debug) GM_log("ucpThread: " + ucpThread.toString()); vote.printStatus(); if (ucpThread.challengeDefinition().scoreType().match(/PIC-P/)) { previousVote = cummulativeScore; previousVote.options.poster = vote.options.poster; } else { previousVote = vote; } /* TODO: if (vote.poster().username === playername && !vote.hasError()) { vote.setProvideDiscussLink(true); } */ }); previousVote = null; //overwrite some base statusses if challenge is in voting. if (ucpThread.challengeName().match(groupConfig.states().closed)) { // should never happen if (debug) GM_log("overwriting status: " + ucpThread.challengeName() + " matches " + groupConfig.states().closed); ucpThread.updateStatus("closed"); } if (ucpThread.finished(cummulativeScore)) { if (debug) GM_log("finishing early"); ucpThread.updateStatus("finished"); } if (debug) GM_log("checking status"); ucpThread.checkStatus(photosposted, debug); if (debug) GM_log("ucpThread: " + ucpThread.toString()); // TODO: if vote has just been cast (url/#comment1234..), and is ok, automatically open Discuss page if (ucpThread.challengeStatus() === "Finished" || ucpThread.challengeStatus() === "PlayerFinished" || ucpThread.challengeStatus() === "PlayerVoted" || ucpThread.challengeStatus() === "Voted" || ucpThread.challengeStatus() === "Closed" || ucpThread.challengeStatus() === "Player") { if (ucpThread.challengeDefinition().scoreType().match(/PIC-[HVP]/)) { ucpThread.setScoreSummary(cummulativeScore.showPicResult()); } else if (ucpThread.challengeDefinition().scoreType().match(/RATE-PHOTO|VERTICAL-WEIGHTED/)) { // do nothing } else { ucpThread.setScoreSummary(cummulativeScore.showVotes()); } } if (threadPage && !(ucpThread instanceof UCPUnknownThread)) { if (ucpThread.challengeName().match(/kanchenjunga/i)) { photos = ucpFindPhotos(challengeAnnouncement, ucpThread, true); showPhotoSummary(photos); } else if (ucpThread.challengeName().match(/MOTHER OF THE MONTH SEMI-FINALS/)) { showPhotoSummary(photos); } else if (ucpThread.challengeName().match(/Hall [oO]f Fame/) && groupConfig.groupname() === 'superchallenge') { showPhotoSummary(photos); } else if (groupConfig.groupname().match(/yes_or_no/)) { if (ucpThread.challengeName().match(/15.*contest.*game(x3|3x) winners/i) || (ucpThread.challengeName().match(/game winner/i) && ucpThread.challengeDefinition().scoreType() === "VERTICAL")) { showPhotoSummary(photos); } } else if (groupConfig.groupname() === 'macro-life--challenges') { if (ucpThread.challengeName().match(/^\d{4}\s+COMP/)) { showPhotoSummary(photos); } } } //let's go and change the update status on screen //update thread info ucpThread.setReplies(commentcounter); if (debug) GM_log("storing " + ucpThread.challengeStatus()); ucpThread.store(); ucpThread.printStatus(groupPreferences, ucpLanguage); if (debug) GM_log("status printed"); ucpThread.sort(groupPreferences, topicListingTable); /*if ($chk(threadNr)) { ucpThread.stopprocessing = new Date(); try { GM_log([ threadNr + ":", "time between start of load and incoming file: " + (ucpThread.pageloaded.getTime() - ucpThread.startload.getTime()) + " millis", "time between incoming file and start of processing: " + (ucpThread.startprocessing.getTime() - ucpThread.pageloaded.getTime()) + " millis", "time to process: " + (ucpThread.stopprocessing.getTime() - ucpThread.startprocessing.getTime()) + " millis" ].join('\n')); } catch (e) { GM_log("error in times: " + e); } } */ return; } function getStoredThreadsForGroup(groupname) { var retval = new Hash(); var reMatch = new RegExp("^UCP\\." + groupname + "\\.(\\d+)"); $each(GM_listValues(), function(key) { if (key.match(reMatch)) { var threadInfo = GM_getObject(key); var topicId = key.match(reMatch)[1]; retval.set(topicId, threadInfo); } }); return retval; } function processTopicListingTable(topicListingTable, processCallback) { // this is the perfect place to clean up the local storage var storedThreads = getStoredThreadsForGroup(groupConfig.groupname()); // select main table //var topicListingHeaderRow = document.evaluate(".//tr", topicListingTable, null, // XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; var topicListingHeaderRow = topicListingTable.getElement('tr'); var headerColumns = topicListingHeaderRow.getChildren('th'); var latestPostColumn = headerColumns[3]; if (groupPreferences.ucpStyle()) { latestPostColumn.width = "6%"; latestPostColumn.set('html', latestPostColumn.get('html').replace('Post', '')); } var statusTitleCell = new Element("th", { styles: { width: "5%" } }); if (groupPreferences.ucpFirstColumn()) { statusTitleCell.inject(headerColumns[0], 'before'); } else { statusTitleCell.inject(headerColumns[3], 'after'); } var statusTitleDiv = new Element('div', { styles: { textAlign: 'center' } }).adopt(new Element('a', { html: "UCP-ng", target: '_blank', href: 'http://www.flickr.com/groups/1307178@N20/discuss/72157623600133810/#comment72157623600157950', styles: { cursor: 'help' } })); statusTitleDiv.inject(statusTitleCell); var topicListingRows = topicListingTable.getElements('tr'); // let's loop the table and start processing topicListingRows.each(function (topicRow, i) { if (i === 0) { // headers return; } if (groupPreferences.ucpStyle()) { topicRow.style.overflow = 'hidden'; topicRow.style.height = '1.4em'; topicRow.style.bottomPadding = '0px'; } //var columns = document.evaluate("./td", topicRow, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var columns = topicRow.getChildren('td'); var challengeColumn = columns[0]; if (groupPreferences.ucpStyle()) { // create some extra space for a cleaner layout if (challengeColumn && groupPreferences.ucpRemoveNew()) { // remove the <span class='New'>NEW</span> //var newImg = document.evaluate("./span[@class = 'New']", challengeColumn, null, // XPathResult.FIRST_ORDERED_NODE_TYPE, null) // .singleNodeValue; var newImg = challengeColumn.getElement('span.New'); if (newImg) { newImg.dispose(); // DON'T!! disables the Admin's info button, and checkboxes, if run after Admin Tool //challengeColumn.set('html', challengeColumn.get('html').replace(/ /, '')); } } var authorColumn = columns[1]; if (authorColumn) { // style attributes are necessary on both the td element as on a div element authorColumn.style.whiteSpace = 'nowrap'; authorColumn.style.overflow = 'hidden'; authorColumn.style.height = '1.02em'; authorColumn.style.width = '8em'; var authorColumnDiv = new Element("div", { styles: { whiteSpace: 'nowrap', overflow: 'hidden', height: '1.02em', width: '8em' } }).inject(authorColumn); authorColumn.getElements('a').each(function(authorAnchor) { authorAnchor.dispose(); authorAnchor.inject(authorColumnDiv); }); } latestPostColumn = columns[3]; if (latestPostColumn) { latestPostColumn.style.whiteSpace = 'nowrap'; latestPostColumn.style.overflow = 'hidden'; latestPostColumn.set('html', latestPostColumn.get('html').replace(/ ago/, "") .replace(/minutes/, "min. ") .replace(/seconds/, "sec. ")); } } //var topic = document.evaluate(".//a", columns[0], null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) // .singleNodeValue; var topic = challengeColumn.getElement('a'); var chlgname = topic.textContent; var ucpThread = ucpCreateChallengeThread({ chlgAnchor: topic, url: topic.href, chlgname: chlgname, groupConfig: groupConfig, needsStatusOnTopicListing: true }); ucpThread.retrieve(); // remove from list of stored threads: what's left can be removed from storage var topicId = topic.href.match(/.*flickr.com\/groups\/[^\/]+\/discuss\/(\d+)\//)[1]; storedThreads.erase(topicId); //debug = chlgname.match('Naveesh'); //if (debug) GM_log("retrieved " + ucpThread.challengeStatus()); // reset var skipChallenge = groupConfig.skipChallenge(ucpThread); var counterColumn = columns[2]; var commentcounter = parseInt(counterColumn.get('html'), 10); // add statusses var statusCell = new Element('td', { styles: { textAlign: 'center' }, vAling: 'center' }).adopt(myanchor = new Element('a', { id: "UCPNG." + topic.href, href: topic.href })); if (groupPreferences.ucpFirstColumn()) { statusCell.inject(columns[0], 'before'); } else { statusCell.inject(columns[3], 'after'); } ucpThread.setLabelElement(myanchor); ucpThread.setScoreAnchor(counterColumn); var loadneeded = false; if (!skipChallenge) // not "---" { // read UCP.http://www.flickr.com/groups/name/discuss/.lastloadtime var elapstime = Math.round(CPStartTime.getTime() / 1000) - ucpThread.lastLoadTime(); // no matter what, if it closed as an Error the last time, reload! if (ucpThread.challengeStatus().match("ERROR")) { loadneeded = true; } else if (!ucpThread.lastLoadTime() || elapstime > 60 * 3) { //more then 3 minutes: force reload (after enough testing, this could be set to 1h) //GM_log("reloading '" + ucpThread.challengeName() + "' based on elapsed time = " + (elapstime/60) + " min."); loadneeded = true; } else { if (ucpThread.challengeName() === undefined) { // sentinel not found //GM_log("reloading based on missing name"); loadneeded = true; } else { if (!ucpThread.validVotingAsStored()) { GM_log("reloading based on invalid voting"); loadneeded = true; } if (ucpThread.replies() !== commentcounter) { //GM_log("reloading based on commentcounter"); loadneeded = true; // some groups (The Pinnacle) reuse threads // reparse fully if replies decrease if (ucpThread.replies() > commentcounter) { ucpThread.setChallengeStatus("none"); // remove VOTE or FINISHED } } if (ucpThread.challengeStatus() === "UPDATING") { //GM_log("reloading based on status"); loadneeded = true; } } } // 3way reuses showroom threads for icon voting if (ucpThread.challengeName() !== chlgname) { //GM_log("reloading based on name change"); loadneeded = true; } else if (ucpThread instanceof UCPNonChallengeThread) { loadneeded = false; } if (loadneeded && ucpThread.validVotingAsStored() && ((ucpThread.challengeStatus() === 'Open' || ucpThread.challengeStatus() === 'OK' || ucpThread.challengeStatus() === 'Filled') && ucpThread.replies() === commentcounter && ucpThread.challengeName() === chlgname)) { loadneeded = false; } //DEBUG //loadneeded = true; // in case the name changed, make sure the rest of the script uses the new name ucpThread.setChallengeName(chlgname); if (loadneeded) { ucpThread.printStatus(groupPreferences, ucpLanguage, "UPDATING"); ucpThread.resetStatus(); ucpThread.loadthread(groupPreferences, processCallback); } else { var challengeConfig = ucpThread.challengeDefinition(); if (ucpThread.challengeStatus().match("Player") && // "PlayerVoted" || "PlayerMustVote" || "PlayerMayVote" || PlayerFinished challengeConfig.countsToLimit()) { ++playernumber; //when loadneeded => number gets added in loadthread } ucpThread.printStatus(groupPreferences, ucpLanguage); ucpThread.sort(groupPreferences, topicListingTable); } } else { // "---" // differentiate between two types '---': // closed vs. unknown ucpThread.resetStatus(); ucpThread.printStatus(groupPreferences, ucpLanguage); ucpThread.sort(groupPreferences, topicListingTable); } }); storedThreads.getKeys().each(function (key) { GM_deleteValue("UCP." + groupname + '.' + key); }); //GM_log("End of processing main discuss page"); return; } // ******************* // Start of processing // ******************* if (window.name === 'Log page') { return; //don't process log page } // if version is newer than stored one, clear all GM variables var storedVersion; if (!$chk(GM_getValue("UCP.version"))) { cleanupUCPvariables(); } else { storedVersion = GM_getValue("UCP.version"); // VO.5.9 if (CPtoolversion !== storedVersion) { cleanupUCPvariables(); } } GM_setValue("UCP.version", CPtoolversion); // special case: iframe with meta data from userscripts.org if (document.location.href.match('http://userscripts.org/scripts/source/' + scriptNumber + '.meta.js')) { storeVersion(); return; } checkVersion(); // check if we have GM variables if (!$chk(GM_getValue("UCP.lastloadtime." + groupname))) { GM_setValue("UCP.lastloadtime." + groupname, Math.round(CPStartTime.getTime() / 1000)); } // // read UCP.http://www.flickr.com/groups/name/discuss/.lastloadtime var lastloadtime = parseInt(GM_getValue("UCP.lastloadtime." + groupname), 10) * 1000; var elapstime = CPStartTime.getTime() - lastloadtime; if (elapstime > 1000 * 60 * 60) //more than 1 hour : cleanup { // clear all GM_values for this group cleanupUCPvariables(groupname); } // store UCP.http://www.flickr.com/groups/name/discuss/.lastloadtime GM_setValue("UCP.lastloadtime." + groupname, Math.round(CPStartTime.getTime() / 1000)); var discussPage = false; var threadPage = false; var pendingItemsPage = false; var supportedGroupOrPage = false; var groupNavigationMenu; if (document.location.href.match(/.*\/edit\/?/)) { GM_log("not processing edit page"); return; } else if (document.location.href.match(/.*flickr.com\/groups\/1221507@N24\/discuss(?:\/)?$/)) { // the Syndicate GM_log("on syndicate page; ignoring"); return; } else if (document.location.href.match(/.*flickr.com\/groups\/1221507@N24\/discuss\//)) { // some Syndicate page GM_log("some syndicate page; ignoring"); return; } else if (document.location.href.match(/.*flickr.com\/groups\/.*\/discuss(?:\/page\d+)?(?:\/)?$/)) { GM_log("on discuss page"); discussPage = true; groupNavigationMenu = $('cattington_outer'); var navigation = $$('div#Main table#SubNav tbody tr').getElement('td.Section'); supportedGroupOrPage = initialize(); } else if (document.location.href.match(/.*flickr.com\/groups\/.*\/discuss\/\d+/)) { // can be followed with '/', '/page', '/#comment', '/#reply', ... GM_log("on challenge thread page"); threadPage = true; var navigation = $$('div#Main h1#Tertiary'); supportedGroupOrPage = initialize(); } else if (document.location.href.match(/.*flickr.com\/groups\/.*\/admin\/pending\//)) { GM_log("on pending stuff page"); pendingItemsPage = true; var navigation = $$('div#Main table#SubNav tbody tr').getElement('td.Section'); supportedGroupOrPage = initialize(); } if (!supportedGroupOrPage) { return; } if (discussPage === true) { // only append [preferences] on Discuss page groupNavigationMenu.adopt( new Element('p', { 'class': "LinksNewP", id: 'UCheckPlayNGPreferences' })).adopt( new Element('span', { styles: { fontWeight: 'bold' }, html: "UCheckPlayNG preferences:" })).adopt( new Element('a', { name: '#', title: "Click to change preferences", styles: { color: "Blue", cursor: "pointer", }, html: " [ " + (groupPreferences.ucpStyle() ? "UCP style" : "Flickr style") + " - " + (groupPreferences.language() ? groupPreferences.language() : "English") + " ] ", events: { click: togglePreferencesDialog } }), new Element('a', { html: " [ Supported Challenge Groups ]", name: "#", title: "Click to access the list of supported challenge groups", styles: { color: "Blue", cursor: "pointer" }, events: { click: toggleGroupList } }) ); } // create field for messages (reached limit, not logged in, ..) navigation.adopt(mydiv = new Element('div', { styles: { display: 'none' }, id: "UCheckPlayNGStatusDiv" }).adopt( new Element('p', { id: "UCheckPlayNGStatus", styles: { textDecoration: 'none', color: 'Red' } }))); if (discussPage) { if (topicListingTable) { processTopicListingTable(topicListingTable, processDiscussionTopic); } else { GM_log("not processing (no TopicListing found!)"); } } else if (threadPage) { // only process when started from page1 var page = document.location.href.match(/\/page(\d+)/); if (page) { page = parseInt(page[1], 10); } if (!page || page === 1) { var discussionTopic = $('DiscussTopic'); var challengeHeader = $('Main').getElement('td#GoodStuff').getElement('h2'); var chlgname = challengeHeader.textContent; var thread = document.location.href; var ucpThread = ucpCreateChallengeThread({ url: thread, chlgname: chlgname, groupConfig: groupConfig, needsStatusOnChallengePage: true }); ucpThread.retrieve(); ucpThread.setLastLoadTime(Math.round(CPStartTime.getTime() / 1000)); ucpThread.setScoreAnchor(challengeHeader); ucpThread.setChallengeName(chlgname); // the stored name may be different, and no longer valid ucpThread.resetStatus(); //GM_log("test: " + ucpThread.challengeDefinition()); // multiple pages? var nextButton = null; if ($chk($('GoodStuff').getElement('div.Paginator'))) { nextButton = $('GoodStuff').getElement('div.Paginator').getElement('a.Next'); } if ($chk(nextButton)) { $('GoodStuff').getElements('div.Pages').dispose(); } try { ucpThread.loadNextTopicPage($chk(nextButton) ? nextButton.href : null, discussionTopic, processDiscussionTopic); } catch (e) { GM_log("error loading next page: " + e); } } else { var ucpStatusDiv = $('UCheckPlayNGStatusDiv'); var ucpStatus = $('UCheckPlayNGStatus'); ucpStatus.set('html', " (no UCP checking on page " + page + ")"); ucpStatusDiv.style.display = ''; ucpStatus.style.color = 'red'; } } else if (pendingItemsPage) { // nothing to do: for the admin script } // check library updates ucpGroupConfigReader.checkForUpdates(groupname); ucpLanguageConfigReader.checkForUpdates(groupPreferences.language()); return; // ******************* // End of processing // ******************* })();
Mozilla add on,User script,Grease Monkey Script, greasemonkey userscripts, updater userscripts mafia wars userscripts mafia wars autoplayer userscripts mafia wars wall userscripts scripts userscripts travian greasemonkey greasemonkey download greasemonkey facebook greasemonkey tutorial greasemonkey youtube greasemonkey travian greasemonkey chrome greasemonkey mafia wars greasemonkey mafia wars autoplayer
Sunday, January 23, 2011
Unified CheckPlay for Flickr Challenge Groups
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment