5
\$\begingroup\$

Background

I was working on a StackOverflow helper program (that sends me notifications) when I noticed something rather interesting in StackOverflow's Network tab:

enter image description here

StackOverflow uses WebSockets (not too surprising, but good to know)! For this challenge, we will be conquering StackOverflow's web socket server.

This particular method could be transferred over to other domains related to StackExchange, but StackOverflow is very popular and a good example. Feel free to lookup tags for other domains on StackExchange (feel free to connect to those instead, although I doubt it would reduce byte count since StackOverflow has id 1).

StackOverflow's Unofficial WebSocket API

There are two separate WebSocket servers you could use:

  • The secure, wss://qa.sockets.stackexchange.com/
  • Or the insecure, ws://qa.sockets.stackexchange.com/

Since we are code golfing and we aren't going to be sending any not-already-public-information, I suspect people will lean more toward the insecure option.

Next, in order to subscribe to our favorite tag, we need to send the following to the WebSocket to 'subscribe' to the tag's feed.

1-questions-newest-tag-javascript

You can replace javascript with any tag we're interested in.

The server will occasional send heartbeat requests. You must reply with hb (so you can keep the connection alive).

A heartbeat requests looks as follows:

 {"action":"hb","data":"hb"}`

Here's a basic JavaScript-based socket (it may take a several minutes for there to be a new post, depending on time of day). I used the most popular tag JavaScript to test with, but if you know of a more popular tag, go ahead and leave a comment.

Example:

var exampleSocket = new WebSocket("wss://qa.sockets.stackexchange.com/");
exampleSocket.onopen = function (event) {
  exampleSocket.send("1-questions-newest-tag-javascript"); 
};
exampleSocket.onmessage = function (event) {
  if (JSON.parse(event.data).data === "hb") {
    exampleSocket.send("hb")
  }
  console.log(event.data)
};

Specifications

Input: Any tag the user wants to receive updates about.

Output: Should be the first question (from when you connected to the WebSocket) and all consecutive available for said tag on StackOverflow.

Rules:

  • You must use WebSockets. Or, at least connect to sockets.stackexchange.com (including, ws://qa.sockets.stackexchange.com) and use it to receive updates for the inputted tag. If you can find a better way of doing it (like not using the 1-questions-newest-tag-<tag> but still receive updates fo the inputted tag) by all means. Or if you can write your own WebSocket equivalent in less bytes, even cooler.
  • Least number of bytes wins!

Language Compatibility

External libraries are encouraged. Many programming languages have a compatible library.

\$\endgroup\$
  • \$\begingroup\$ What is an acceptable output? Do we need to parse the response, and only show the markup in the data field? \$\endgroup\$ – Carcigenicate Dec 11 '17 at 21:18
  • \$\begingroup\$ @Carcigenicate You can print the whole thing or just parts of it. \$\endgroup\$ – Neil Dec 11 '17 at 21:21
  • \$\begingroup\$ So the raw JSON string is fine? \$\endgroup\$ – Carcigenicate Dec 11 '17 at 21:32
  • \$\begingroup\$ And is anything required between response prints? Do the responses need to be separated by a newline? \$\endgroup\$ – Carcigenicate Dec 11 '17 at 21:35
  • 1
    \$\begingroup\$ +1 for the effort required to provide us with the unofficial api \$\endgroup\$ – Poke Dec 15 '17 at 15:57
2
\$\begingroup\$

Clojure with gniazdo, 126, 204, 203 bytes

+78 bytes to handle reacting to heartbeat messages

-1 byte by changing from indexing the string converted to a vector, to just using nth. I was expecting more of a gain from that :/

(use '[gniazdo.core])(fn[t](let[a(atom 0)s #(send-msg @a %)o(connect"ws://qa.sockets.stackexchange.com":on-receive #(if(=((vec %)11)\h)(s"hb")(println %)))](reset! a o)(s(str"1-questions-newest-tag-"t))))

An anonymous function that takes a string tag, and prints the raw JSON results as they're received, while responding to heartbeats.

To test if the received message is a heartbeat, I'm testing if the 12th character of the response is the character h. After looking over this page, it seems impossible for h to be the 12th character without the response being a heartbeat. If this isn't the case, I can try to figure out a workaround.

(ns bits.golf.web-sock
  (:require [gniazdo.core :as g]))

(defn new-tag-posts [^String tag]
  (let [; A mutable state to hold the sock.
        ; Necessary since the sock's callback requires a reference to the
        ;  sock itself
        sock-a (atom nil)

        ; Shortcut function to save bytes
        send #(g/send-msg @sock-a %)

        handler #(if (= (nth % 11) \h) ; If the 12th character (at index 11) is 'h'
                   (send "hb") ; respond to the heartbeat
                   (println %)) ; else print the JSON

        sock (g/connect "ws://qa.sockets.stackexchange.com"
                        :on-receive handler)]

    ; Set the mutable state to the created socket
    (reset! sock-a sock)

    ; Subscribe to the given tag
    (send (str "1-questions-newest-tag-" tag))))
\$\endgroup\$
1
\$\begingroup\$

Groovy, 388 without the @Grab

@Grab('org.java-websocket:Java-WebSocket:1.3.6')
import org.java_websocket.client.*;import org.java_websocket.handshake.*
new WebSocketClient(new URI("ws://qa.sockets.stackexchange.com/")){void onOpen(ServerHandshake h){send("1-questions-newest-tag-"+args[0]);}
void onClose(int c,String r,boolean b){}
void onMessage(String m){println(m);if(m.indexOf('"action":"hb"')>0){send('hb');}}
void onError(Exception e){}}.connect();
while(1){}

With the @Grab directive, run with groovy snippet.groovy javascript

Without the @grab directive, you have to download the library, and run groovy -cp Java-WebSocket-1.3.6.jar snippet.groovy javascript

\$\endgroup\$
  • \$\begingroup\$ Whether you choose the @Grab directive or the command line option, those bytes are part of your score. I think you could get away with giving the JAR a one-byte name, in which case -cp x  will only cost you 6 bytes. \$\endgroup\$ – Jordan Dec 15 '17 at 17:31
1
\$\begingroup\$

Bash, 353 346 333 + 11 = 344 bytes

Builtins only, no coreutils. +11 bytes for -O extglob option.

The script contains unprintable characters, which are represented by e.g. <81> (a hex pair) or <CR> below. Also note there is a trailing space on lines 2 and 5. A full xxd dump follows..

Invoke like this:

$ bash -O extglob script.sh javascript

Output is on STDERR.

Note: This requires that your copy of bash was built with the --enable-net-redirections compile option. The default bash on OS X was not, but the version on Homebrew was (brew install bash && $(brew --prefix)/bin/bash ...).

exec</dev/tcp/qa.sockets.stackexchange.com/80>&0
p=printf\ 
$p"GET / HTTP/1.1
Host: x
Upgrade: 
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

"
r=read;$r;$r;$r;$r;$r
$p"<81>\\`$p%o $((${#1}+151))`\0\0\0\0001-questions-newest-tag-$1"
g()($p<81><82><01><01><01><01>ic
sleep 5
g);g&
h()($r -d} D
$p"${D##?@(~?|<7F>???????|)?}}">&2
h);h

xxd dump

<!--
00000000: 6578 6563 3c2f 6465 762f 7463 702f 7161  exec</dev/tcp/qa
00000010: 2e73 6f63 6b65 7473 2e73 7461 636b 6578  .sockets.stackex
00000020: 6368 616e 6765 2e63 6f6d 2f38 303e 2630  change.com/80>&0
00000030: 0a70 3d70 7269 6e74 665c 200a 2470 2247  .p=printf\ .$p"G
00000040: 4554 202f 2048 5454 502f 312e 310a 486f  ET / HTTP/1.1.Ho
00000050: 7374 3a20 780a 5570 6772 6164 653a 200a  st: x.Upgrade: .
00000060: 5365 632d 5765 6253 6f63 6b65 742d 4b65  Sec-WebSocket-Ke
00000070: 793a 2064 4768 6c49 484e 6862 5842 735a  y: dGhlIHNhbXBsZ
00000080: 5342 7562 3235 6a5a 513d 3d0a 5365 632d  SBub25jZQ==.Sec-
00000090: 5765 6253 6f63 6b65 742d 5665 7273 696f  WebSocket-Versio
000000a0: 6e3a 2031 330a 0a22 0a72 3d72 6561 643b  n: 13..".r=read;
000000b0: 2472 3b24 723b 2472 3b24 723b 2472 0a24  $r;$r;$r;$r;$r.$
000000c0: 7022 815c 5c60 2470 256f 2024 2828 247b  p".\\`$p%o $((${
000000d0: 2331 7d2b 3135 3129 2960 5c30 5c30 5c30  #1}+151))`\0\0\0
000000e0: 5c30 3030 312d 7175 6573 7469 6f6e 732d  \0001-questions-
000000f0: 6e65 7765 7374 2d74 6167 2d24 3122 0a67  newest-tag-$1".g
00000100: 2829 2824 7081 8201 0101 0169 630a 736c  ()($p......ic.sl
00000110: 6565 7020 350a 6729 3b67 260a 6828 2928  eep 5.g);g&.h()(
00000120: 2472 202d 647d 2044 0a24 7022 247b 4423  $r -d} D.$p"${D#
00000130: 233f 4028 7e3f 7c7f 3f3f 3f3f 3f3f 3f7c  #?@(~?|.???????|
00000140: 293f 7d7d 223e 2632 0a68 293b 68         )?}}">&2.h);h
-->

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.