Talk:1190: Time

Explain xkcd: It's 'cause you're dumb.
(Difference between revisions)
Jump to: navigation, search
Line 581: Line 581:
  
 
[[Special:Contributions/194.114.62.72|194.114.62.72]] I'm pretty shure it's not the seaside, but a lake - the water level is not changing at all.
 
[[Special:Contributions/194.114.62.72|194.114.62.72]] I'm pretty shure it's not the seaside, but a lake - the water level is not changing at all.
 +
 +
Its probably going to loop back on itself, eventually, and repeat this way forever. Its already started. [[Special:Contributions/113.160.224.209|113.160.224.209]] 07:12, 27 March 2013 (UTC)
  
 
== Javascript Explanation ==
 
== Javascript Explanation ==
  
 
For those wondering about the Javascript behind this: I posted my analysis of the Javascript [http://www.echochamber.me/viewtopic.php?f=7&t=101043&start=760#p3303579 on the xkcd forums], and further de-obfuscated and annotated the code over on [https://gist.github.com/cincodenada/5246094 GitHub].  Here's a quick summary though: it holds open a connection to xkcd's servers and listens for instructions and follows them. Those instructions are either "load a new image" or "reload the page". So, you don't have to mash F5, it will automatically update the image when they're available. We have no way to control how fast the images come or when they do, and it's quite possible for them to update forever. --[[User:Fiveofoh|Fiveofoh]] ([[User talk:Fiveofoh|talk]]) 06:41, 27 March 2013 (UTC)
 
For those wondering about the Javascript behind this: I posted my analysis of the Javascript [http://www.echochamber.me/viewtopic.php?f=7&t=101043&start=760#p3303579 on the xkcd forums], and further de-obfuscated and annotated the code over on [https://gist.github.com/cincodenada/5246094 GitHub].  Here's a quick summary though: it holds open a connection to xkcd's servers and listens for instructions and follows them. Those instructions are either "load a new image" or "reload the page". So, you don't have to mash F5, it will automatically update the image when they're available. We have no way to control how fast the images come or when they do, and it's quite possible for them to update forever. --[[User:Fiveofoh|Fiveofoh]] ([[User talk:Fiveofoh|talk]]) 06:41, 27 March 2013 (UTC)

Revision as of 07:12, 27 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)
Thanks for explaining. Why hasn't anyone posted this before? Could "location.hash" possibly have anything to do with the method used to generate the image hash key? Also, why is this code so difficult to follow (Obfuscation?)? So many questions... Sorry if this is just a huge waste of Time.
location is the URI of the page. location.hash is the part of the uri after the # character. If you go to https://xkcd.com/1190/#verbose, you'll see some debugging output in your browser's debugging console (Firefox: Web Console or Firebug, Chrome: Development Tools). But nothing to decode the algorithm... :-( --83.243.48.2 10:01, 25 March 2013 (UTC)
Well, I don't know what's doing it, but there's definitely some script (probably this script) that's refreshing the image automatically. I left the comic open for an hour or so and noticed the image had changed. I refreshed with #verbose in Chrome right before the 30 minute mark and got the following in the console.
connecting to event source: http://c0.xkcd.com/stream/comic/time?method=EventSource time07.min.js:1
s {type: "comic/time", data: "{"spread":5,"image":"832a7f13ca0fadc46e93475bb617d78211e32c81c3af0e289a51f8f149707759.png"}", lastEventId: "e2992bf0-9557-11e2-8001-1c6f659cb250"} time07.min.js:1
waiting 0 seconds before displaying comic 832a7f13ca0fadc46e93475bb617d78211e32c81c3af0e289a51f8f149707759.png time07.min.js:1
Resource interpreted as Image but transferred with MIME type application/octet-stream: "http://xkcd.com/events/connect_start". time07.min.js:1
s {type: "comic/time", data: "{"spread":5,"image":"847265673986f085460bf1a95b96f7171bcd9a4f1f0a598b2188307d03bcfaa3.png"}", lastEventId: "79580fe8-9558-11e2-8001-1c6f659cb250"} time07.min.js:1
waiting 4 seconds before displaying comic 847265673986f085460bf1a95b96f7171bcd9a4f1f0a598b2188307d03bcfaa3.png time07.min.js:1
connection error i {type: "error"} time07.min.js:1
Resource interpreted as Image but transferred with MIME type application/octet-stream: "http://xkcd.com/events/connect_error".
The script seems to poll the server every minute or two. It's different from before, where the image server itself redirected to the correct image. The auto refresh was probably always intended, but not quite ready when the comic went live. It may have turned out to be necessary too, so the image server doesn't have to do all the work. 129.21.119.153 14:45, 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)

http://imgs.xkcd.com/comics/time/426033682a26a0012a6f8e0c47287af91b7991a852d81c77402c937ffbd650c6.png

http://www.explainxkcd.com/wiki/images/1/1e/f46c6571393bee1ee649a7daae41f6328e63482506aef1e22607d22c47dd7027.png --Johnsmith (talk) 22:51, 25 March 2013 (UTC)

http://www.explainxkcd.com/wiki/images/b/b0/88e3a0c8bba935c669606d9134314f811a0961985f968dd5d329e4695acc67c8.png --Johnsmith (talk) 23:10, 25 March 2013 (UTC)


Is it just me or or did Randall manage to make all of us perform a Denial of Service on xkcd.com, and explainxkcd.com ? xkcd.com seems much slower, and I keep getting "500 Internal server error" when accessing this site (explainxkcd.com). I guess that's the effect of having everybody hit F5 every few minutes :) 193.239.192.194 11:57, 25 March 2013 (UTC)

Earlier today, the server handled all the image redirections. The script you see above went through several mutations (currently at #8), with each mutation it seems that Randall is adding more servers and trying to split the load between them. This is basically how a bot-net works - we all run code written by some evil genius, and he's changing the code as time passes to serve some hidden purpose. 79.180.173.88 15:44, 25 March 2013 (UTC)

   If he is using us as a botnet, then maybe the next comic will be something alluding to that.
   Probably like this: http://xkcd.com/350/

When I saw this comic last night and that there was no explanation up, I thought to myself "How zen." I figured that Randall was going through a calm streak before throwing us the utterly ridiculous April 1st comic. Did it come early, or does he have something even bigger planned for us? 76.106.251.87 07:05, 26 March 2013 (UTC)

Sorry, did you miss the bit where this comic updates every 30 minutes and all the server error messages being caused by the massive traffic to both the wiki and the main xkcd website? Davidy²²[talk] 07:08, 26 March 2013 (UTC)
Well, when I said "last night" and "no explanation", I implied that I wasn't aware of that at the time, which is why I thought what I did. Of course, it is now "now" and there is an explanation, so that should answer your question. Also, since it's not April 1st, and Randall has consistently released something major on that day, the jury is still out, leaving my question quite open (though I was really only asking for opinions). 76.106.251.87 07:20, 26 March 2013 (UTC)


wanted to add an image to the list above, but didn't know at what timestamp to add it, got 69085b480cb82911b19fe8f114909756989eed89b0d227db0f59c1843de7ba24.png at 2013-03-26 09:47 CET (UTC+0100)

/Puggan
The hours denote the time since the initial release of the comic. The page is still a work in progress, we're going to bring that all into one image file soon. Davidy²²[talk] 09:13, 26 March 2013 (UTC)

This site should seriously consider cloudflare, it's perfect at times like this and takes minutes to set up. I run all my sites through it and it saves a lot of page huts and bandwidth. 123.3.136.228Evan Pyle

Or at least make the main page a static page that refreshes every so often. I'm guessing that most of the traffic is going to the front page with not as much traffic to the actual comic page Odysseus654 (talk) 15:43, 26 March 2013 (UTC)

Some of the images on the wiki (looks like time38.png through time48.png) are slightly different than what is on the main site. The lines are slightly thicker, as though someone did them based on screen captures. Royce (talk) 14:37, 26 March 2013 (UTC)

Well, at least we have the hashes so they can be re-retrieved, so nothing is really lost, right? Should we add links to the original? Odysseus654 (talk) 15:43, 26 March 2013 (UTC)
I uploaded two of the "thicker" images and one of the "regular" ones, and I did the same thing for all of them: right-click->save-as. Given that the "thick" ones are all clustered together, I think the files on the xkcd site changed. Druid816 (talk) 18:21, 26 March 2013 (UTC)

Story so far: linky Odysseus654 (talk) 19:30, 26 March 2013 (UTC)

I guess we shall find out in ~10 minutes if Randall is trolling us. 129.138.30.95 04:20, 27 March 2013 (UTC)

... so that's it?

Did I just miss something or we've all been epically trolled for 48 hours? 189.59.175.92 04:22, 27 March 2013 (UTC)

Motherofgod, no, he's *still* going! Davidy²²[talk] 05:20, 27 March 2013 (UTC)

What makes you think he's done? 129.138.30.95 04:25, 27 March 2013 (UTC)

I'm still waiting for the water level to drop precipitously... and then for red spiders to run over everything Odysseus654 (talk) 04:28, 27 March 2013 (UTC)

Well, strip 1191 is up so I assumed it was over. I guess it's not. Until April's Fools maybe? 189.59.175.92 04:32, 27 March 2013 (UTC)

It is not over -- the image is still updating, at least it did for me Spongebog (talk)
Yes, it's not over. Last frame shows just a minimal movement of Cueball's head, but no doubt it's still ongoing. 189.59.175.92 04:49, 27 March 2013 (UTC)
Given the fact the strips for the last 2 weeks have been comparatively simple, I expect Randall has been planning this for at least that long. 101.98.156.239 (talk) (please sign your comments with ~~~~)

Considering the common theme with "today's" strip, anyone wanna guess that he's sending us a hex-encoded file over a really slow modem link, slated to complete April 1? Anyone wanna run "magic" over the hashes and see if they come up with a compression codec or something? Odysseus654 (talk) 04:45, 27 March 2013 (UTC)

194.114.62.72 I'm pretty shure it's not the seaside, but a lake - the water level is not changing at all.

Its probably going to loop back on itself, eventually, and repeat this way forever. Its already started. 113.160.224.209 07:12, 27 March 2013 (UTC)

Javascript Explanation

For those wondering about the Javascript behind this: I posted my analysis of the Javascript on the xkcd forums, and further de-obfuscated and annotated the code over on GitHub. Here's a quick summary though: it holds open a connection to xkcd's servers and listens for instructions and follows them. Those instructions are either "load a new image" or "reload the page". So, you don't have to mash F5, it will automatically update the image when they're available. We have no way to control how fast the images come or when they do, and it's quite possible for them to update forever. --Fiveofoh (talk) 06:41, 27 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?