Talk:1190: Time

Explain xkcd: It's 'cause you're dumb.
(Difference between revisions)
Jump to: navigation, search
(Before obfuscation...: new section)
Line 286: Line 286:
  
 
:: ... actually, given that the script part doesn't seem to do anything just now, it's even possible it's for later (ie, starts producing images when the correct time come). Or maybe there is a bug somewhere in the code :-). -- [[User:Hkmaly|Hkmaly]] ([[User talk:Hkmaly|talk]]) 09:27, 25 March 2013 (UTC)
 
:: ... actually, given that the script part doesn't seem to do anything just now, it's even possible it's for later (ie, starts producing images when the correct time come). Or maybe there is a bug somewhere in the code :-). -- [[User:Hkmaly|Hkmaly]] ([[User talk:Hkmaly|talk]]) 09:27, 25 March 2013 (UTC)
 +
 +
== Before obfuscation... ==
 +
 +
(function (global) {
 +
    "use strict";
 +
 +
    function Map() {
 +
        this.data = {}
 +
    }
 +
    function EventTarget() {
 +
        this.listeners = new Map
 +
    }
 +
    function throwError(e) {
 +
        setTimeout(function () {
 +
            throw e
 +
        }, 0)
 +
    }
 +
    function Event(type) {
 +
        this.type = type
 +
    }
 +
    function MessageEvent(type, options) {
 +
        Event.call(this, type), this.data = options.data, this.lastEventId = options.lastEventId
 +
    }
 +
    function getDuration(value, def) {
 +
        var n = Number(value);
 +
        return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || def
 +
    }
 +
    function fire(that, property, event) {
 +
        try {
 +
            typeof that[property] == "function" && that[property](event)
 +
        } catch (e) {
 +
            throwError(e)
 +
        }
 +
    }
 +
    function EventSource(url, options) {
 +
        function close() {
 +
            currentState = CLOSED, xhr !== null && (xhr.abort(), xhr = null), timeout !== 0 && (clearTimeout(timeout), timeout = 0), that.readyState = CLOSED
 +
        }
 +
        function onProgress(isLoadEnd) {
 +
            var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText || "" : "",
 +
                event = null;
 +
            if (currentState === CONNECTING) {
 +
                var contentType = isXHR ? responseText !== "" ? xhr.getResponseHeader("Content-Type") : "" : xhr.contentType;
 +
                if (contentType && contentTypeRegExp.test(contentType)) {
 +
                    currentState = OPEN, wasActivity = !0, retry = initialRetry, that.readyState = OPEN, event = new Event("open"), that.dispatchEvent(event), fire(that, "onopen", event);
 +
                    if (currentState === CLOSED) return
 +
                }
 +
            }
 +
            if (currentState === OPEN) {
 +
                responseText.length > charOffset && (wasAct = !0, wasActivity = !0);
 +
                var i = 0,
 +
                    i1 = responseText.indexOf("\r", charOffset),
 +
                    i2 = responseText.indexOf("\n", charOffset);
 +
                while (i1 !== -1 || i2 !== -1) {
 +
                    i1 === -1 || i2 !== -1 && i2 < i1 ? (i = i2, i2 = responseText.indexOf("\n", i + 1)) : (i = i1, i1 = responseText.indexOf("\r", i + 1));
 +
                    var line = responseText.slice(charOffset, i),
 +
                        oldWasCR = wasCR;
 +
                    wasCR = responseText.slice(i, i + 1) === "\r", charOffset = i + 1;
 +
                    if (!oldWasCR || line.length !== 0 || wasCR) {
 +
                        responseBuffer.push(line);
 +
                        var field = responseBuffer.join("");
 +
                        responseBuffer.length = 0;
 +
                        if (field !== "") {
 +
                            var value = "",
 +
                                j = field.indexOf(":", 0);
 +
                            j !== -1 && (value = field.slice(j + (field.slice(j + 1, j + 2) === " " ? 2 : 1)), field = field.slice(0, j)), field === "data" ? dataBuffer.push(value) : field === "id" ? lastEventIdBuffer = value : field === "event" ? eventTypeBuffer = value : field === "retry" ? (initialRetry = getDuration(value, initialRetry), retry = initialRetry, retryLimit < initialRetry && (retryLimit = initialRetry)) : field === "retryLimit" ? retryLimit = getDuration(value, retryLimit) : field === "heartbeatTimeout" && (heartbeatTimeout = getDuration(value, heartbeatTimeout), timeout !== 0 && (clearTimeout(timeout), timeout = setTimeout(onTimeout, heartbeatTimeout)))
 +
                        } else {
 +
                            if (dataBuffer.length !== 0) {
 +
                                lastEventId = lastEventIdBuffer;
 +
                                var type = eventTypeBuffer || "message";
 +
                                event = new MessageEvent(type, {
 +
                                    data: dataBuffer.join("\n"),
 +
                                    lastEventId: lastEventIdBuffer
 +
                                }), that.dispatchEvent(event), type === "message" && fire(that, "onmessage", event);
 +
                                if (currentState === CLOSED) return
 +
                            }
 +
                            dataBuffer.length = 0, eventTypeBuffer = ""
 +
                        }
 +
                    }
 +
                }
 +
                charOffset !== responseText.length && (responseBuffer.push(responseText.slice(charOffset)), charOffset = responseText.length)
 +
            }
 +
            wasAct && progressTimeout === 0 && (wasAct = !1, progressTimeout = setTimeout(p, 80)), currentState !== OPEN && currentState !== CONNECTING || !(isLoadEnd || charOffset > 1048576 || timeout === 0 && !wasActivity) ? timeout === 0 && (wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout)) : (currentState = WAITING, xhr.abort(), timeout !== 0 && (clearTimeout(timeout), timeout = 0), retry > retryLimit && (retry = retryLimit), timeout = setTimeout(onTimeout, retry), retry = retry * 2 + 1, that.readyState = CONNECTING, event = new Event("error"), that.dispatchEvent(event), fire(that, "onerror", event))
 +
        }
 +
        function p() {
 +
            progressTimeout = 0, onProgress(!1)
 +
        }
 +
        function onProgress2() {
 +
            onProgress(!1)
 +
        }
 +
        function onLoadEnd() {
 +
            onProgress(!0)
 +
        }
 +
        function onTimeout() {
 +
            timeout = 0;
 +
            if (currentState !== WAITING) {
 +
                onProgress(!1);
 +
                return
 +
            }
 +
            if (navigator.onLine === !1) {
 +
                timeout = setTimeout(onTimeout, 500);
 +
                return
 +
            }
 +
            if (webkitBefore535 && global.document && (global.document.readyState === "loading" || global.document.readyState === "interactive")) {
 +
                timeout = setTimeout(onTimeout, 100);
 +
                return
 +
            }
 +
            xhr.onload = xhr.onerror = onLoadEnd, xhr.mozAnon === undefined ? xhr.onprogress = onProgress2 : xhr.onreadystatechange = onProgress2, wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout), charOffset = 0, currentState = CONNECTING, dataBuffer.length = 0, eventTypeBuffer = "", lastEventIdBuffer = lastEventId, responseBuffer.length = 0, wasCR = !1, xhr.open("GET", url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random() + 1).slice(2)), !0), xhr.withCredentials = withCredentials, xhr.responseType = "text", isXHR && xhr.setRequestHeader("Accept", "text/event-stream"), xhr.send(null)
 +
        }
 +
        url = String(url);
 +
        var withCredentials = Boolean(xhr2 && options && options.withCredentials),
 +
            initialRetry = getDuration(options ? options.retry : NaN, 1e3),
 +
            retryLimit = getDuration(options ? options.retryLimit : NaN, 3e5),
 +
            heartbeatTimeout = getDuration(options ? options.heartbeatTimeout : NaN, 45e3),
 +
            lastEventId = options && options.lastEventId && String(options.lastEventId) || "",
 +
            that = this,
 +
            retry = initialRetry,
 +
            wasActivity = !1,
 +
            xhr = new Transport,
 +
            timeout = 0,
 +
            charOffset = 0,
 +
            currentState = WAITING,
 +
            dataBuffer = [],
 +
            lastEventIdBuffer = "",
 +
            eventTypeBuffer = "",
 +
            responseBuffer = [],
 +
            wasCR = !1,
 +
            progressTimeout = 0,
 +
            wasAct = !1;
 +
        options = null, EventTarget.call(this), this.close = close, this.url = url, this.readyState = CONNECTING, this.withCredentials = withCredentials, onTimeout()
 +
    }
 +
    function F() {
 +
        this.CONNECTING = CONNECTING, this.OPEN = OPEN, this.CLOSED = CLOSED
 +
    }
 +
    Map.prototype = {
 +
        get: function (key) {
 +
            return this.data[key + "~"]
 +
        },
 +
        set: function (key, value) {
 +
            this.data[key + "~"] = value
 +
        },
 +
        "delete": function (key) {
 +
            delete this.data[key + "~"]
 +
        }
 +
    }, EventTarget.prototype = {
 +
        dispatchEvent: function (event) {
 +
            var type = String(event.type),
 +
                listeners = this.listeners,
 +
                typeListeners = listeners.get(type);
 +
            if (!typeListeners) return;
 +
            var length = typeListeners.length,
 +
                i = -1;
 +
            while (++i < length) {
 +
                var listener = typeListeners[i];
 +
                try {
 +
                    listener.call(this, event)
 +
                } catch (e) {
 +
                    throwError(e)
 +
                }
 +
            }
 +
        },
 +
        addEventListener: function (type, callback) {
 +
            type = String(type);
 +
            var listeners = this.listeners,
 +
                typeListeners = listeners.get(type);
 +
            typeListeners || listeners.set(type, typeListeners = []);
 +
            var i = typeListeners.length;
 +
            while (--i >= 0) if (typeListeners[i] === callback) return;
 +
            typeListeners.push(callback)
 +
        },
 +
        removeEventListener: function (type, callback) {
 +
            type = String(type);
 +
            var listeners = this.listeners,
 +
                typeListeners = listeners.get(type);
 +
            if (!typeListeners) return;
 +
            var length = typeListeners.length,
 +
                filtered = [],
 +
                i = -1;
 +
            while (++i < length) typeListeners[i] !== callback && filtered.push(typeListeners[i]);
 +
            filtered.length === 0 ? listeners["delete"](type) : listeners.set(type, filtered)
 +
        }
 +
    }, MessageEvent.prototype = Event.prototype;
 +
    var XHR = global.XMLHttpRequest,
 +
        XDR = global.XDomainRequest,
 +
        xhr2 = Boolean(XHR && (new XHR).withCredentials !== undefined),
 +
        isXHR = xhr2,
 +
        Transport = xhr2 ? XHR : XDR,
 +
        WAITING = -1,
 +
        CONNECTING = 0,
 +
        OPEN = 1,
 +
        CLOSED = 2,
 +
        contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,
 +
        webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
 +
    F.prototype = EventTarget.prototype, EventSource.prototype = new F, F.call(EventSource), Transport && (global.EventSource = EventSource)
 +
})(this),
 +
function () {
 +
    function record(name) {
 +
        (new Image).src = "http://xkcd.com/events/" + name
 +
    }
 +
    function log() {
 +
        location.hash == "#verbose" && console.log.apply(console, arguments)
 +
    }
 +
    try {
 +
        var esURL = "http://c0.xkcd.com/stream/comic/time?method=EventSource",
 +
            source = new EventSource(esURL);
 +
        log("connecting to event source:", esURL), source.addEventListener("open", function (ev) {
 +
            record("connect_start")
 +
        }, !1), source.addEventListener("error", function (ev) {
 +
            record("connect_error")
 +
        }, !1), source.addEventListener("loadtest", log, !1), source.addEventListener("comic/time", log, !1), source.addEventListener("comic/time", function (ev) {
 +
            var data = JSON.parse(ev.data),
 +
                img = document.getElementById("comic").getElementsByTagName("img")[0],
 +
                delay = Math.round(Math.random() * data.spread);
 +
            log("waiting", delay, "seconds before displaying comic", data.image), setTimeout(function () {
 +
                img.src = "http://imgs.xkcd.com/comics/time/" + data.image
 +
            }, delay * 1e3)
 +
        }, !1)
 +
    } catch (e) {
 +
        record("js_error")
 +
    }
 +
}();
 +
 +
[[Special:Contributions/79.180.173.88|79.180.173.88]] 09:48, 25 March 2013 (UTC)

Revision as of 09:48, 25 March 2013

Pretty sure we're just getting trolled with this one 99.108.190.136 04:48, 25 March 2013 (UTC)

Can't tell if this is emo xkcd or trolling xkcd. Alpha (talk) 04:53, 25 March 2013 (UTC)


Something seems a little fishy because the image url is different than normal. Bugefun (talk) 04:55, 25 March 2013 (UTC)

Maybe the comic slowly changes throughout the day. Alpha (talk) 04:56, 25 March 2013 (UTC)
Oh god, it does. Alpha (talk) 04:57, 25 March 2013 (UTC)
When uploading different versions of the image, use the naming convention time[iterationNumber].png. We'll compile all the images into one and display them as per Traffic Lights. Davidy²²[talk] 05:05, 25 March 2013 (UTC)
Alright, so the comic appears to be switching between two states here: between this and this. If nothing new happens, I'll get to clipping the comics together. Davidy²²[talk] 05:28, 25 March 2013 (UTC)
Whoop, nope, this just came up. Is there more to come? Davidy²²[talk] 05:34, 25 March 2013 (UTC)
Alright, so a new one is posted every half-hour. Whoopee. Davidy²²[talk] 06:06, 25 March 2013 (UTC)
And there's a new one! Megan leaning back and looking up...
Well, the image changed, who has the time to make a script to catch the new images and compile them into a gif? https://dl.dropbox.com/u/932170/time.png Statharas.903 (talk) 07:14, 25 March 2013 (UTC)

72.21.198.66 05:11, 25 March 2013 (UTC)It could be a reference to the old proverb " time and tide wait for none" Cueball and the girl could be waiting for the tide in the beach! (Just a guess)72.21.198.66 05:11, 25 March 2013 (UTC)

The picture does chance with time. The URL includes a changing timestamp that I can't decipher. Compare these two URLS (which have slightly different images: http://imgs.xkcd.com/comics/time/8eb156cce408df8bb83528382d6a2aa2ce6c74f3c573fd12b058cd1c56420672.png http://imgs.xkcd.com/comics/time/1e349a579b5f9b5ed487ddf7e88244b70330941ddedac9c6abf6ed2e3f589b97.png

Perhaps there is a way to hack the URL to view future images. 199.30.248.121 05:29, 25 March 2013 (UTC)

I would also like to add that knowing randall, these are not the only images. For all we know, the image will still be changing in 5 years while a tree grows in front of them. My point is: Are the URLs hackable, or did he encrypt them? 199.30.248.121 05:33, 25 March 2013 (UTC)

Likely there is a way to hack the URLs; they look like some sort of hash, probably a hashed timestamp. Of course, he could easily have added some salt to the hash, making it significantly *harder* to hack. But they're strings of a specific length, so it should be pretty easy to bruteforce it, fetch all the images, and then (maybe) reverse-engineer the sequence. *That* all depends on how many of them there are. 76.90.249.178 05:44, 25 March 2013 (UTC)

Good god, do you see how many digits are *in* that hash? The sun'll have burned out by the time we've tested every possible combination of digits. Davidy²²[talk] 05:47, 25 March 2013 (UTC)

It seems that the image is updated every 1/2 hour. 152.23.97.150 06:17, 25 March 2013 (UTC)

Given that the images switch back and forth between other images already seen, and that the comic should be viewable in the future, it seems unlikely that it's any thing like a simple sha256 of part of the timestamp. I think it's more likely a function of half-hours and minutes (assuming we continue to get a new possible image every half-hour). 99.153.248.206 06:59, 25 March 2013 (UTC)
The images do cycle, yes. But for some reason I have never seen the img where Megan is looking behind her. Also wouldn't it be difficult to show a sequential story (like the rising tide) if the previous images keep cycling ?

Hash appears to be SHA-256. I tried some obvious hashes ("1", "11901", "1190_1", "1190.1") to no avail. Maybe this is HMAC-SHA256? Also, I would suggest trying Unix timestamps. 131.156.236.149 06:19, 25 March 2013 (UTC)

I've been trying to make educated guesses as to what's being hashed here: http://www.xorbin.com/tools/sha256-hash-calculator ... he could also be using hash(hash2(value)) which would be virtually impossible to crack. 99.153.248.206 06:59, 25 March 2013 (UTC)

It's entirely possible that the "hash" is actually randomly generated. Just a thought. 129.21.119.153 07:03, 25 March 2013 (UTC)

Alright, this is probably not going to work, but I'm trying to exploit Randall's awesomeness here. Maybe he decided to take the time-stamps from the user? I don't know if that's even possible... That would then allow people in different time zones to obtain different images simultaneously. (What's the corollary of Godwin's law for a bunch of math-and-science nerds and relativity? Is there one?) Clicking the img src url on the comic's html page, give me this: http://imgs.xkcd.com/comics/time/752687b61523144c61736cd89f8c153dc41e19128f72d78d44947ff800f057fa.png : Never mind.. apparently others see the same image too.

Could he be doing this live? Monitoring the discussion on the net? Collaborative, crowdsourced comic-ing? Reminds me of those you-decide-what-the-character-does-next-and-flip-to-appropriate-page parallel plot novels.

220.224.246.97 07:14, 25 March 2013 (UTC)

Let's just compare the two pictures and see how the bottom right changes, which I believe is water and they are indeed waiting for the tide. Statharas.903 (talk) 07:19, 25 March 2013 (UTC)

I'm adding urls to pictures bellow, edit freely.
They change every 5 minutes, will try to keep track.

http://www.explainxkcd.com/wiki/images/f/f8/time.png http://imgs.xkcd.com/comics/time/1e349a579b5f9b5ed487ddf7e88244b70330941ddedac9c6abf6ed2e3f589b97.png http://imgs.xkcd.com/comics/time/752687b61523144c61736cd89f8c153dc41e19128f72d78d44947ff800f057fa.png http://dl.dropbox.com/u/932170/timeasdf.png http://dl.dropbox.com/u/932170/time6.png

I have uploaded all the different images onto the wiki, in the order that they were revealed. To avoid needless duplication of effort, I'll put them up in the explanation page. Davidy²²[talk] 07:44, 25 March 2013 (UTC)

It just went back to the second image... 220.224.246.97 07:59, 25 March 2013 (UTC)

And now changed to something new. http://imgs.xkcd.com/comics/time/cdcc6b46b32c53f8596cd0106958b42c4260b9cbc022e6d94054147aa6554960.png
The images do look alike, but they're all different. Thanks David. Statharas.903 (talk) 08:04, 25 March 2013 (UTC)
No..I checked the random string. They're exactly the same. In fact, now it's gone back to the second image. Again. 220.224.246.97 08:07, 25 March 2013 (UTC)

Just found this JavaScript code embedded in the comic HTML source (Update: Reformatted to prevent eye-bleeding):

(function (e) {

   "use strict";
   function t() {
       this.data = {}
   }
   function n() {
       this.listeners = new t
   }
   function r(e) {
       setTimeout(function () {
           throw e
       }, 0)
   }
   function i(e) {
       this.type = e
   }
   function s(e, t) {
       i.call(this, e), this.data = t.data, this.lastEventId = t.lastEventId
   }
   function g(e, t) {
       var n = Number(e);
       return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || t
   }
   function y(e, t, n) {
       try {
           typeof e[t] == "function" && e[t](n)
       } catch (i) {
           r(i)
       }
   }
   function b(t, r) {
       function B() {
           L = d, N !== null && (N.abort(), N = null), C !== 0 && (clearTimeout(C), C = 0), S.readyState = d
       }
       function j(e) {
           var t = L === p || L === h ? N.responseText || "" : "",
               n = null;
           if (L === h) {
               var r = f ? t !== "" ? N.getResponseHeader("Content-Type") : "" : N.contentType;
               if (r && v.test(r)) {
                   L = p, T = !0, x = u, S.readyState = p, n = new i("open"), S.dispatchEvent(n), y(S, "onopen", n);
                   if (L === d) return
               }
           }
           if (L === p) {
               t.length > k && (H = !0, T = !0);
               var o = 0,
                   a = t.indexOf("\r", k),
                   l = t.indexOf("\n", k);
               while (a !== -1 || l !== -1) {
                   a === -1 || l !== -1 && l < a ? (o = l, l = t.indexOf("\n", o + 1)) : (o = a, a = t.indexOf("\r", o + 1));
                   var m = t.slice(k, o),
                       B = D;
                   D = t.slice(o, o + 1) === "\r", k = o + 1;
                   if (!B || m.length !== 0 || D) {
                       _.push(m);
                       var j = _.join("");
                       _.length = 0;
                       if (j !== "") {
                           var I = "",
                               q = j.indexOf(":", 0);
                           q !== -1 && (I = j.slice(q + (j.slice(q + 1, q + 2) === " " ? 2 : 1)), j = j.slice(0, q)), j === "data" ? A.push(I) : j === "id" ? O = I : j === "event" ? M = I : j === "retry" ? (u = g(I, u), x = u, b < u && (b = u)) : j === "retryLimit" ? b = g(I, b) : j === "heartbeatTimeout" && (w = g(I, w), C !== 0 && (clearTimeout(C), C = setTimeout(R, w)))
                       } else {
                           if (A.length !== 0) {
                               E = O;
                               var U = M || "message";
                               n = new s(U, {
                                   data: A.join("\n"),
                                   lastEventId: O
                               }), S.dispatchEvent(n), U === "message" && y(S, "onmessage", n);
                               if (L === d) return
                           }
                           A.length = 0, M = ""
                       }
                   }
               }
               k !== t.length && (_.push(t.slice(k)), k = t.length)
           }
           H && P === 0 && (H = !1, P = setTimeout(F, 80)), L !== p && L !== h || !(e || k > 1048576 || C === 0 && !T) ? C === 0 && (T = !1, C = setTimeout(R, w)) : (L = c, N.abort(), C !== 0 && (clearTimeout(C), C = 0), x > b && (x = b), C = setTimeout(R, x), x = x * 2 + 1, S.readyState = h, n = new i("error"), S.dispatchEvent(n), y(S, "onerror", n))
       }
       function F() {
           P = 0, j(!1)
       }
       function I() {
           j(!1)
       }
       function q() {
           j(!0)
       }
       function R() {
           C = 0;
           if (L !== c) {
               j(!1);
               return
           }
           if (navigator.onLine === !1) {
               C = setTimeout(R, 500);
               return
           }
           if (m && e.document && (e.document.readyState === "loading" || e.document.readyState === "interactive")) {
               C = setTimeout(R, 100);
               return
           }
           N.onload = N.onerror = q, N.mozAnon === undefined ? N.onprogress = I : N.onreadystatechange = I, T = !1, C = setTimeout(R, w), k = 0, L = h, A.length = 0, M = "", O = E, _.length = 0, D = !1, N.open("GET", t + ((t.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(E) + "&r=" + String(Math.random() + 1).slice(2)), !0), N.withCredentials = o, N.responseType = "text", f && N.setRequestHeader("Accept", "text/event-stream"), N.send(null)
       }
       t = String(t);
       var o = Boolean(a && r && r.withCredentials),
           u = g(r ? r.retry : NaN, 1e3),
           b = g(r ? r.retryLimit : NaN, 3e5),
           w = g(r ? r.heartbeatTimeout : NaN, 45e3),
           E = r && r.lastEventId && String(r.lastEventId) || "",
           S = this,
           x = u,
           T = !1,
           N = new l,
           C = 0,
           k = 0,
           L = c,
           A = [],
           O = "",
           M = "",
           _ = [],
           D = !1,
           P = 0,
           H = !1;
       r = null, n.call(this), this.close = B, this.url = t, this.readyState = h, this.withCredentials = o, R()
   }
   function w() {
       this.CONNECTING = h, this.OPEN = p, this.CLOSED = d
   }
   t.prototype = {
       get: function (e) {
           return this.data[e + "~"]
       },
       set: function (e, t) {
           this.data[e + "~"] = t
       },
       "delete": function (e) {
           delete this.data[e + "~"]
       }
   }, n.prototype = {
       dispatchEvent: function (e) {
           var t = String(e.type),
               n = this.listeners,
               i = n.get(t);
           if (!i) return;
           var s = i.length,
               o = -1;
           while (++o < s) {
               var u = i[o];
               try {
                   u.call(this, e)
               } catch (a) {
                   r(a)
               }
           }
       },
       addEventListener: function (e, t) {
           e = String(e);
           var n = this.listeners,
               r = n.get(e);
           r || n.set(e, r = []);
           var i = r.length;
           while (--i >= 0) if (r[i] === t) return;
           r.push(t)
       },
       removeEventListener: function (e, t) {
           e = String(e);
           var n = this.listeners,
               r = n.get(e);
           if (!r) return;
           var i = r.length,
               s = [],
               o = -1;
           while (++o < i) r[o] !== t && s.push(r[o]);
           s.length === 0 ? n["delete"](e) : n.set(e, s)
       }
   }, s.prototype = i.prototype;
   var o = e.XMLHttpRequest,
       u = e.XDomainRequest,
       a = Boolean(o && (new o).withCredentials !== undefined),
       f = a,
       l = a ? o : u,
       c = -1,
       h = 0,
       p = 1,
       d = 2,
       v = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,
       m = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
   w.prototype = n.prototype, b.prototype = new w, w.call(b), l && (e.EventSource = b)

})(this), function () {

   function e(e) {
       (new Image).src = "http://xkcd.com/events/" + e
   }
   function t() {
       location.hash == "#verbose" && console.log.apply(console, arguments)
   }
   try {
       var n = "http://c0.xkcd.com/stream/comic/time?method=EventSource",
           r = new EventSource(n);
       t("connecting to event source:", n), r.addEventListener("open", function (t) {
           e("connect_start")
       }, !1), r.addEventListener("error", function (t) {
           e("connect_error")
       }, !1), r.addEventListener("loadtest", t, !1), r.addEventListener("comic/time", t, !1), r.addEventListener("comic/time", function (e) {
           var n = JSON.parse(e.data),
               r = document.getElementById("comic").getElementsByTagName("img")[0],
               i = Math.round(Math.random() * n.spread);
           t("waiting", i, "seconds before displaying comic", n.image), setTimeout(function () {
               r.src = "http://imgs.xkcd.com/comics/time/" + n.image
           }, i * 1e3)
       }, !1)
   } catch (i) {
       e("js_error")
   }

}();

I'm no programmer but this looks important to me...

Doesn't really help. The script basically changes the image when something happens (probably some time passes, although it's possible there is more hidden there). WHAT image then appears is not directed by the script, but by the site. Specifically, the image displayed as first is taken from http://c0.xkcd.com/redirect/comic/time, while the script asks for http://c0.xkcd.com/stream/comic/time?method=EventSource&r=(somenumber) ... which is, if you get correct "r", probably some json containing the image url. So, even if you hack the script, you will not get all possible urls. -- Hkmaly (talk) 09:17, 25 March 2013 (UTC)
... actually, given that the script part doesn't seem to do anything just now, it's even possible it's for later (ie, starts producing images when the correct time come). Or maybe there is a bug somewhere in the code :-). -- Hkmaly (talk) 09:27, 25 March 2013 (UTC)

Before obfuscation...

(function (global) {

   "use strict";
   function Map() {
       this.data = {}
   }
   function EventTarget() {
       this.listeners = new Map
   }
   function throwError(e) {
       setTimeout(function () {
           throw e
       }, 0)
   }
   function Event(type) {
       this.type = type
   }
   function MessageEvent(type, options) {
       Event.call(this, type), this.data = options.data, this.lastEventId = options.lastEventId
   }
   function getDuration(value, def) {
       var n = Number(value);
       return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || def
   }
   function fire(that, property, event) {
       try {
           typeof that[property] == "function" && that[property](event)
       } catch (e) {
           throwError(e)
       }
   }
   function EventSource(url, options) {
       function close() {
           currentState = CLOSED, xhr !== null && (xhr.abort(), xhr = null), timeout !== 0 && (clearTimeout(timeout), timeout = 0), that.readyState = CLOSED
       }
       function onProgress(isLoadEnd) {
           var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText || "" : "",
               event = null;
           if (currentState === CONNECTING) {
               var contentType = isXHR ? responseText !== "" ? xhr.getResponseHeader("Content-Type") : "" : xhr.contentType;
               if (contentType && contentTypeRegExp.test(contentType)) {
                   currentState = OPEN, wasActivity = !0, retry = initialRetry, that.readyState = OPEN, event = new Event("open"), that.dispatchEvent(event), fire(that, "onopen", event);
                   if (currentState === CLOSED) return
               }
           }
           if (currentState === OPEN) {
               responseText.length > charOffset && (wasAct = !0, wasActivity = !0);
               var i = 0,
                   i1 = responseText.indexOf("\r", charOffset),
                   i2 = responseText.indexOf("\n", charOffset);
               while (i1 !== -1 || i2 !== -1) {
                   i1 === -1 || i2 !== -1 && i2 < i1 ? (i = i2, i2 = responseText.indexOf("\n", i + 1)) : (i = i1, i1 = responseText.indexOf("\r", i + 1));
                   var line = responseText.slice(charOffset, i),
                       oldWasCR = wasCR;
                   wasCR = responseText.slice(i, i + 1) === "\r", charOffset = i + 1;
                   if (!oldWasCR || line.length !== 0 || wasCR) {
                       responseBuffer.push(line);
                       var field = responseBuffer.join("");
                       responseBuffer.length = 0;
                       if (field !== "") {
                           var value = "",
                               j = field.indexOf(":", 0);
                           j !== -1 && (value = field.slice(j + (field.slice(j + 1, j + 2) === " " ? 2 : 1)), field = field.slice(0, j)), field === "data" ? dataBuffer.push(value) : field === "id" ? lastEventIdBuffer = value : field === "event" ? eventTypeBuffer = value : field === "retry" ? (initialRetry = getDuration(value, initialRetry), retry = initialRetry, retryLimit < initialRetry && (retryLimit = initialRetry)) : field === "retryLimit" ? retryLimit = getDuration(value, retryLimit) : field === "heartbeatTimeout" && (heartbeatTimeout = getDuration(value, heartbeatTimeout), timeout !== 0 && (clearTimeout(timeout), timeout = setTimeout(onTimeout, heartbeatTimeout)))
                       } else {
                           if (dataBuffer.length !== 0) {
                               lastEventId = lastEventIdBuffer;
                               var type = eventTypeBuffer || "message";
                               event = new MessageEvent(type, {
                                   data: dataBuffer.join("\n"),
                                   lastEventId: lastEventIdBuffer
                               }), that.dispatchEvent(event), type === "message" && fire(that, "onmessage", event);
                               if (currentState === CLOSED) return
                           }
                           dataBuffer.length = 0, eventTypeBuffer = ""
                       }
                   }
               }
               charOffset !== responseText.length && (responseBuffer.push(responseText.slice(charOffset)), charOffset = responseText.length)
           }
           wasAct && progressTimeout === 0 && (wasAct = !1, progressTimeout = setTimeout(p, 80)), currentState !== OPEN && currentState !== CONNECTING || !(isLoadEnd || charOffset > 1048576 || timeout === 0 && !wasActivity) ? timeout === 0 && (wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout)) : (currentState = WAITING, xhr.abort(), timeout !== 0 && (clearTimeout(timeout), timeout = 0), retry > retryLimit && (retry = retryLimit), timeout = setTimeout(onTimeout, retry), retry = retry * 2 + 1, that.readyState = CONNECTING, event = new Event("error"), that.dispatchEvent(event), fire(that, "onerror", event))
       }
       function p() {
           progressTimeout = 0, onProgress(!1)
       }
       function onProgress2() {
           onProgress(!1)
       }
       function onLoadEnd() {
           onProgress(!0)
       }
       function onTimeout() {
           timeout = 0;
           if (currentState !== WAITING) {
               onProgress(!1);
               return
           }
           if (navigator.onLine === !1) {
               timeout = setTimeout(onTimeout, 500);
               return
           }
           if (webkitBefore535 && global.document && (global.document.readyState === "loading" || global.document.readyState === "interactive")) {
               timeout = setTimeout(onTimeout, 100);
               return
           }
           xhr.onload = xhr.onerror = onLoadEnd, xhr.mozAnon === undefined ? xhr.onprogress = onProgress2 : xhr.onreadystatechange = onProgress2, wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout), charOffset = 0, currentState = CONNECTING, dataBuffer.length = 0, eventTypeBuffer = "", lastEventIdBuffer = lastEventId, responseBuffer.length = 0, wasCR = !1, xhr.open("GET", url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random() + 1).slice(2)), !0), xhr.withCredentials = withCredentials, xhr.responseType = "text", isXHR && xhr.setRequestHeader("Accept", "text/event-stream"), xhr.send(null)
       }
       url = String(url);
       var withCredentials = Boolean(xhr2 && options && options.withCredentials),
           initialRetry = getDuration(options ? options.retry : NaN, 1e3),
           retryLimit = getDuration(options ? options.retryLimit : NaN, 3e5),
           heartbeatTimeout = getDuration(options ? options.heartbeatTimeout : NaN, 45e3),
           lastEventId = options && options.lastEventId && String(options.lastEventId) || "",
           that = this,
           retry = initialRetry,
           wasActivity = !1,
           xhr = new Transport,
           timeout = 0,
           charOffset = 0,
           currentState = WAITING,
           dataBuffer = [],
           lastEventIdBuffer = "",
           eventTypeBuffer = "",
           responseBuffer = [],
           wasCR = !1,
           progressTimeout = 0,
           wasAct = !1;
       options = null, EventTarget.call(this), this.close = close, this.url = url, this.readyState = CONNECTING, this.withCredentials = withCredentials, onTimeout()
   }
   function F() {
       this.CONNECTING = CONNECTING, this.OPEN = OPEN, this.CLOSED = CLOSED
   }
   Map.prototype = {
       get: function (key) {
           return this.data[key + "~"]
       },
       set: function (key, value) {
           this.data[key + "~"] = value
       },
       "delete": function (key) {
           delete this.data[key + "~"]
       }
   }, EventTarget.prototype = {
       dispatchEvent: function (event) {
           var type = String(event.type),
               listeners = this.listeners,
               typeListeners = listeners.get(type);
           if (!typeListeners) return;
           var length = typeListeners.length,
               i = -1;
           while (++i < length) {
               var listener = typeListeners[i];
               try {
                   listener.call(this, event)
               } catch (e) {
                   throwError(e)
               }
           }
       },
       addEventListener: function (type, callback) {
           type = String(type);
           var listeners = this.listeners,
               typeListeners = listeners.get(type);
           typeListeners || listeners.set(type, typeListeners = []);
           var i = typeListeners.length;
           while (--i >= 0) if (typeListeners[i] === callback) return;
           typeListeners.push(callback)
       },
       removeEventListener: function (type, callback) {
           type = String(type);
           var listeners = this.listeners,
               typeListeners = listeners.get(type);
           if (!typeListeners) return;
           var length = typeListeners.length,
               filtered = [],
               i = -1;
           while (++i < length) typeListeners[i] !== callback && filtered.push(typeListeners[i]);
           filtered.length === 0 ? listeners["delete"](type) : listeners.set(type, filtered)
       }
   }, MessageEvent.prototype = Event.prototype;
   var XHR = global.XMLHttpRequest,
       XDR = global.XDomainRequest,
       xhr2 = Boolean(XHR && (new XHR).withCredentials !== undefined),
       isXHR = xhr2,
       Transport = xhr2 ? XHR : XDR,
       WAITING = -1,
       CONNECTING = 0,
       OPEN = 1,
       CLOSED = 2,
       contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,
       webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
   F.prototype = EventTarget.prototype, EventSource.prototype = new F, F.call(EventSource), Transport && (global.EventSource = EventSource)
})(this),

function () {

   function record(name) {
       (new Image).src = "http://xkcd.com/events/" + name
   }
   function log() {
       location.hash == "#verbose" && console.log.apply(console, arguments)
   }
   try {
       var esURL = "http://c0.xkcd.com/stream/comic/time?method=EventSource",
           source = new EventSource(esURL);
       log("connecting to event source:", esURL), source.addEventListener("open", function (ev) {
           record("connect_start")
       }, !1), source.addEventListener("error", function (ev) {
           record("connect_error")
       }, !1), source.addEventListener("loadtest", log, !1), source.addEventListener("comic/time", log, !1), source.addEventListener("comic/time", function (ev) {
           var data = JSON.parse(ev.data),
               img = document.getElementById("comic").getElementsByTagName("img")[0],
               delay = Math.round(Math.random() * data.spread);
           log("waiting", delay, "seconds before displaying comic", data.image), setTimeout(function () {
               img.src = "http://imgs.xkcd.com/comics/time/" + data.image
           }, delay * 1e3)
       }, !1)
   } catch (e) {
       record("js_error")
   }

}();

79.180.173.88 09:48, 25 March 2013 (UTC)

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox

It seems you are using noscript, which is stopping our project wonderful ads from working. Explain xkcd uses ads to pay for bandwidth, and we manually approve all our advertisers, and our ads are restricted to unobtrusive images and slow animated GIFs. If you found this site helpful, please consider whitelisting us.

Want to advertise with us, or donate to us with Paypal or Bitcoin?