Opening a SocketIO object with https results in hang

I tried using the code here: Issuing challenges through the API

I am trying to join a game and play moves in it. However, the SocketIO object hangs trying to do the SSL handshake. Has anyone else had the problem/found a solution?

Let me ping @anoek here and I will dig through come code I have to see if I have any examples.

Edit: Can you post the code you are using? Also, if you are interested we have a Slack channel for OGS development. PM me an email address and I will add you.

No problem. I am running python 3.6 with Anaconda on the Linux subsystem on Windows 10. The code I have is the following: (I am just trying to play a move in this (https://online-go.com/game/8610273) game, getting constant communication shouldn’t be too hard past that once I have the socket up and running) I am aware that the code will crash after the socket is established because I am missing the on_response method, but I will cross that bridge when I get there :slight_smile:

import requests
import time
from socketIO_client import SocketIO
from subprocess import call


s = requests.Session()
ogs_link = 'http://online-go.com'

home = s.get(ogs_link + '/api')
auth = s.post('https://online-go.com/oauth2/token/', data={"username":"theonly747beast", "password":"NotMyPassword", "client_id":"NotMyId", "client_secret": "NotMySecret", "grant_type": "password"})

oauth2_json = auth.json()
access_token = oauth2_json['access_token']
refresh_token = oauth2_json['refresh_token']

prof = s.get('https://online-go.com/api/v1/me/',
		headers = {
			'Authorization': 'Bearer {}'.format(access_token),
		},
)

def rank_to_display(rank_value):
	if rank_value < 30:
		return '%dk' % (30-rank_value,)
	else:
		return '%dd' % ((rank_value-30)+1)

my_info_json = prof.json()

# print(my_info_json)
# print()
print('overall: {}, blitz: {}, live: {}, corr: {}'.format(
	rank_to_display(my_info_json['ranking']),
	rank_to_display(my_info_json['ranking_blitz']),
	rank_to_display(my_info_json['ranking_live']),
	rank_to_display(my_info_json['ranking_correspondence']),
))
print()

#code for making a challenge, I just want to make a move in an existing game for now.
# challenge = s.post('https://online-go.com/api/v1/challenges',
# 		headers = {
# 			'Authorization': 'Bearer {}'.format(access_token),
# 			'Content-Type': 'application/json'
# 		},
# 		json = {
# 			'game': {
# 				'name': 'Test Game -- Please don\'t join!',
# 				'rules': 'japanese',
# 				'ranked': False,
# 				'handicap': 0,
# 				'time_control': 'simple',
# 				"time_control_parameters": {
# 					'time_control': 'simple',
#     				"per_move": 30
#   				},
# 				'pause_on_weekends': False,
# 				'width': 19,
# 				'height': 19,
# 				'disable_analysis': False,
# 			},
# 			'challenger_color': 'automatic',
# 			'min_ranking': 0,
# 			'max_ranking': 10,
# 		},
# 		allow_redirects = False,
# );

# print(challenge.json())
# print("Hello")
with SocketIO('https://ggs.online-go.com/socket.io') as socketIO:
        print("socket is connected.")
        socketIO.emit("game/connect", 
                    {'game_id': 8610273, 'player_id': playerid, 'chat': 1, 'game_type': "game", 'auth':access_token},
                     on_response)
        socketIO.wait_for_callbacks(seconds=3)
        
        # try submitting a move
        socketIO.emit("game/move",{"auth":access_token,"game_id":8610273,"player_id":playerid,"move":"cb"},
                      on_response)
        socketIO.wait_for_callbacks(seconds=3)

When I run it, it prints out the following (which is correct) and does not halt:

overall: 25k, blitz: 25k, live: 25k, corr: 25k

When I press Ctrl+C to kill it, it prints out the following stack trace:

Traceback (most recent call last):                                                                                                    
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/logs.py", line 29, in  _yield_warning_screen                  yield elapsed_time                                                                                                                
Exception: [engine.io waiting for connection] hostname 'ggs.online-go.com' doesn't match either of '*.idviu.io', 'idviu.io'                                                                                                                                                 During handling of the above exception, another exception occurred:                                                                                                                                                                                                         
Traceback (most recent call last):                                                                                                      
File "testingstuff.py", line 71, in <module>                                                                                            
with SocketIO('https://ggs.online-go.com/socket.io') as socketIO:                                                                   
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 353, in __init__                          resource, hurry_interval_in_seconds, **kw)                                                                                          
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 54, in __init__                           self._transport                                                                                                                     
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 62, in _transport                         self._engineIO_session = self._get_engineIO_session()                                                                               
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 83, in _get_engineIO_session              warning_screen.throw(warning)                                                                                                       
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/logs.py", line 35, in _yield_warning_screen                  time.sleep(1)                                                                                                                     
KeyboardInterrupt

Also, when I try to use http, it runs into a StopIteration error and crashes.

Another thing that was mentioned in the linked thread was that challenges timed out after a few seconds of being up but were otherwise fine. Is there a way around this?

(Another side note, I have reached my max new threads limit for the first day, so I’m happy to respond to a PM as long as you start it)

Ok, so, this is weird. ggs.online-go.com returns a certificate for *.idviu.io, which is obviously not valid. I don’t know off the top of my head, but I’d assume you could tell Socket.io to ignore this cert mismatch.

@anoek uhhh…any idea why you have a seemingly random certificate on that endpoint?

Oh, edit, meant to add that I tested and got the same thing - so it’s definitely not some intermediate certificate on his connection.

Here’s the cert I get back from an openssl s_client connection:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            46:c5:d8:2d:50:6b:a6:a5:91:49:4c:4d:c4:d2:de:1c
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=FR, ST=Paris, L=Paris, O=Gandi, CN=Gandi Standard SSL CA 2
        Validity
            Not Before: Oct  5 00:00:00 2016 GMT
            Not After : Oct  5 23:59:59 2018 GMT
        Subject: OU=Domain Control Validated, OU=Gandi Standard Wildcard SSL, CN=*.idviu.io
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c8:cc:80:a5:c5:98:40:7f:7a:a5:bd:c7:c0:69:
                    6d:13:20:eb:8f:e4:fe:3c:dc:51:aa:70:aa:29:d7:
                    92:f1:4a:7b:23:21:76:9e:f9:a8:b9:48:6f:4f:64:
                    3c:b5:80:2b:bf:e1:c6:3d:94:50:50:85:4f:7c:45:
                    10:b1:bf:c1:fb:70:00:ee:93:c3:06:2d:43:f1:2c:
                    ed:4a:26:8a:22:ac:1a:03:0d:54:c8:38:e9:d0:d9:
                    3c:ff:0b:31:cc:4d:29:46:b9:69:7a:12:bd:3e:84:
                    83:a6:44:d6:0b:03:35:c9:0b:3d:fd:d5:5d:13:3b:
                    b7:78:ff:63:a2:71:66:ff:30:c3:41:f0:fd:a0:de:
                    5c:77:91:72:8e:e5:6e:3b:14:2a:54:15:35:c7:9e:
                    7b:f7:a4:6b:ba:32:cc:7d:81:de:f2:57:98:f5:77:
                    b1:93:f6:8f:50:67:f5:e6:1f:9e:d5:c1:ab:79:b4:
                    80:77:85:00:8c:70:34:d2:a2:bd:e0:f2:0d:5f:f6:
                    b5:56:e3:3a:a7:78:77:05:0c:b3:cf:03:7d:fa:66:
                    6c:93:0c:1a:e8:01:24:34:d6:e2:aa:6f:f9:2d:fd:
                    80:de:b8:66:82:84:5c:b5:0c:b3:dc:b4:d5:41:b4:
                    d9:fa:ec:d0:e6:47:3d:d8:bf:cd:07:6a:aa:16:54:
                    24:d9
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:B3:90:A7:D8:C9:AF:4E:CD:61:3C:9F:7C:AD:5D:7F:41:FD:69:30:EA

        X509v3 Subject Key Identifier:
            D3:FA:25:87:50:00:D9:29:1B:D3:D9:F5:69:97:DD:01:A9:71:D0:89
        X509v3 Key Usage: critical
            Digital Signature, Key Encipherment
        X509v3 Basic Constraints: critical
            CA:FALSE
        X509v3 Extended Key Usage:
            TLS Web Server Authentication, TLS Web Client Authentication
        X509v3 Certificate Policies:
            Policy: 1.3.6.1.4.1.6449.1.2.2.26
              CPS: https://cps.usertrust.com
            Policy: 2.23.140.1.2.1

        X509v3 CRL Distribution Points:

            Full Name:
              URI:http://crl.usertrust.com/GandiStandardSSLCA2.crl

        Authority Information Access:
            CA Issuers - URI:http://crt.usertrust.com/GandiStandardSSLCA2.crt
            OCSP - URI:http://ocsp.usertrust.com

        X509v3 Subject Alternative Name:
            DNS:*.idviu.io, DNS:idviu.io
Signature Algorithm: sha256WithRSAEncryption
     33:8d:14:90:e8:2a:d1:01:dd:91:0b:2b:fe:2c:1f:a9:72:09:
     d2:b9:c4:ed:46:79:6f:8e:81:f6:ce:e3:e3:38:be:54:b2:67:
     3a:87:f7:c4:1a:1f:3f:f8:f5:3a:7e:87:81:76:1d:fe:36:1c:
     4c:af:5a:71:bf:c3:15:5a:2c:9a:c1:ab:bd:20:5e:6b:5a:c6:
     bc:f9:c0:c1:3e:70:28:d9:f2:c9:38:5a:85:ec:3b:db:08:30:
     18:7a:0c:08:fe:14:0a:6f:af:07:4a:f1:0b:3c:8d:80:e9:a8:
     2d:e9:c4:99:96:5c:0d:16:cf:51:dc:8c:a7:de:01:f9:81:e7:
     bf:da:18:70:ce:02:31:49:fd:c4:f0:cd:b5:f8:fb:ef:09:33:
     eb:db:2c:57:9c:ff:09:88:6c:1d:d7:62:59:2a:ba:bc:00:71:
     c6:a1:12:6a:26:81:69:d4:c0:65:86:20:e0:90:1b:17:ac:d5:
     1e:09:1a:e9:3d:c0:03:4a:71:95:b0:d6:53:bc:97:9a:67:95:
     15:3d:dd:64:78:da:f9:99:0d:ab:1a:3d:d6:ce:e0:66:86:fc:
     04:e3:0f:ed:d7:ce:28:89:52:44:ab:64:0f:c4:e3:30:3f:5f:
     c0:35:1e:71:1b:a9:9b:b0:da:fa:e7:88:28:3e:1f:e9:68:c1:
     6d:b8:91:5b

Ok, I figured out how to turn off verification (via the verify=False. This issue can be figured out at a later time, because this fix seems to work), but now there is a crash when it is starting a session, like so:

Traceback (most recent call last):                                                                                                      
File "testingstuff.py", line 71, in <module>                                                                                            
  with SocketIO('https://ggs.online-go.com/socket.io', secure=True, verify=False) as socketIO:
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 353, in __init__                          resource, hurry_interval_in_seconds, **kw)                                                                                          
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 54, in __init__                           self._transport                                                                                                                     
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 62, in _transport                         self._engineIO_session = self._get_engineIO_session()                                                                               
File "/home/jbarratt/anaconda3/lib/python3.6/site-packages/socketIO_client/__init__.py", line 76, in _get_engineIO_session              transport.recv_packet())                                                                                                          
StopIteration

After some snooping, it seems that the Python SocketIO class doesn’t like the fact that ggs.online-go.com/socket.io only returns a “READY” message, and wants more information. Is there any other way to create a socketIO connection using python?

Hiya @the747beast, ggs.online-go.com isn’t used anymore, that was for v4, just replace that with online-go.com and you should be good to go.

I’ve removed the DNS entry for that now to help thwart future confusion.

1 Like

Thanks for the update, @anoek! I changed the URL to online-go.com, and I am now receiving unexpected status code (400 {"code":0,"message":"Transport unknown"}) when I try to connect. The version of socketio I am using is compatible with 1.x; should I be using a version compatible with 0.9?

Nope 1.x is good, we’re on 1.4 or 1.5 iirc - though only the websocket transport is supported

1 Like

@anoek I moved over to Websocket so that I could be sure I was using that (I think before SocketIO was using XHS_PollingTransport, couldn’t get it to use websockets). I am able to contact the online-go.com server, but it responds with a 301 if I try to use ws:// and a 502 if I try and use wss://. Here is my code, and the output:

def on_message(ws, message):
    print("Hi again!")

def on_error(ws, error):
    print("ERROR: ", error)

def on_close(ws):
    print("Goodbye!")

def on_open(ws):
    print("Hello!")

websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://online-go.com",
                          on_message = on_message,
                          on_error = on_error,
                          on_close = on_close)
ws.on_open = on_open
ws.run_forever()

Output:

--- request header ---                                                                                                                
GET / HTTP/1.1                                                                                                                        
Upgrade: websocket                                                                                                                    
Connection: Upgrade                                                                                                                   
Host: online-go.com                                                                                                                   
Origin: http://online-go.com                                                                                                                  
Sec-WebSocket-Key: C35xxfhqURL1INXimQaIXg==                                                                                           
Sec-WebSocket-Version: 13                                                                                                                                                                                                                                                                                                                                                                                         
-----------------------                                                                                                               
--- response header ---                                                                                                               
HTTP/1.1 301 Moved Permanently                                                                                                        
Date: Fri, 05 May 2017 03:39:30 GMT                                                                                                   
Transfer-Encoding: chunked                                                                                                            
Connection: keep-alive                                                                                                                
Set-Cookie: __cfduid=d75c0be03d4f487fc0a8a9e0161765b4e1493955570; expires=Sat, 05-May-18 03:39:30 GMT; path=/; domain=.online-go.com; HttpOnly                                                                                                                              
Cache-Control: max-age=3600                                                                                                           
Expires: Fri, 05 May 2017 04:39:30 GMT                                                                                                
Location: https://online-go.com/                                                                                                      
Server: cloudflare-nginx                                                                                                              
CF-RAY: 35a0ac4b908d6d5a-SJC                                                                                                          
-----------------------                                                                                                               
ERROR:  Handshake status 301                                                                                                          
Goodbye!

Output when ws:// is changed to wss://:

--- request header ---                                                                                                                
GET / HTTP/1.1                                                                                                                        
Upgrade: websocket                                                                                                                    
Connection: Upgrade                                                                                                                   
Host: online-go.com                                                                                                                   
Origin: http://online-go.com                                                                                                          
Sec-WebSocket-Key: jnV7p92WNFcfYcxryNIH/g==                                                                                           
Sec-WebSocket-Version: 13                                                                                                                                                                                                                                                                                                                                                                                         
-----------------------                                                                                                               
--- response header ---                                                                                                               
HTTP/1.1 502 Bad Gateway                                                                                                              
Date: Fri, 05 May 2017 03:43:24 GMT                                                                                                   
Content-Type: text/html; charset=UTF-8                                                                                                
Transfer-Encoding: chunked                                                                                                            
Connection: keep-alive                                                                                                                
Set-Cookie: __cfduid=d0d77c02090c468cfb2464039ff6d91481493955804; expires=Sat, 05-May-18 03:43:24 GMT; path=/; domain=.online-go.com; HttpOnly                                                                                                                              
Expires: Thu, 01 Jan 1970 00:00:01 GMT                                                                                                
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0                                                         Pragma: no-cache                                                                                                                      
X-Frame-Options: SAMEORIGIN                                                                                                           
Server: cloudflare-nginx                                                                                                              
CF-RAY: 35a0b2002f291219-SJC                                                                                                          
-----------------------                                                                                                               
ERROR:  Handshake status 502                                                                                                          
Goodbye!

Hopefully once I can get this connection going it can all go smoothly.

Edit: I was able to get the same behavior when running SocketIO with Websockets only.

I haven’t used the python socket.io libraries so not sure, here’s the node code that works if it’s any help: https://github.com/online-go/gtp2ogs/blob/devel/gtp2ogs.js#L897-L905

For your WebSocket implementation, your problem is that ws is equivalent to http, so you’re getting a 301 sending you to https (which is analogous to wss). You noticed that if you do that you’ll get a 502 because you didn’t specify websocket transport. That’s also easily fixed though, try this:

ws = websocket.WebSocketApp("wss://online-go.com/socket.io/?transport=websocket",on_message = on_message, on_error = on_error, on_close = on_close)
ws.run_forever()

I think part of this is coming down to you somewhat misunderstanding “transport” here. I’m learning this recently too, but in Socket.io, there are two: polling and websocket. You specify that as a query parameter, not based on how you execute it. (Note that this also seems to break Socket.io convention with is (based on my understanding from some cursory Googling) to “upgrade” polling connections to websockets if possible, which is probably why it’s a little bit harder than normal.)

Also, for what it’s worth, here’s how to start a connection with a post. However, requests doesn’t have the ability to continue it past there (wss isn’t implemented, obviously). It gives you the 101 Switching Protocols response that Socket.io is supposed to give when a connection is initiated to switch to websockets. Note Sec-WebSocket-Key is the base64 encoding of any random 16 bytes.

r = requests.get(‘https://online-go.com/socket.io/?EIO=3&transport=websocket’, headers={“Upgrade”:“websocket”, “Connection”:“Upgrade”, “Sec-WebSocket-Key”:“YWJjZGVmZ2hqdHVnZGZ0YQ==”, “Sec-WebSocket-Version”:“13”})

Ok, after digging through the source code for socketIO_client, it seems it’s following the Socket.io standard and starting everything with a polling transport first. You can make it show you what it’s doing more readily (instead of hanging) by adding the keyword argument wait_for_connection=False to the SocketIO constructor. It gives a 400, but if you put in an invalid URL, it actually shows the request string with transport=polling.

>>> sio = SocketIO('https://online-go.com',wait_for_connection=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 353, in __init__
    resource, hurry_interval_in_seconds, **kw)
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 54, in __init__
    self._transport
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 62, in _transport
    self._engineIO_session = self._get_engineIO_session()
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 76, in _get_engineIO_session
    transport.recv_packet())
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/transports.py", line 82, in recv_packet
    **self._kw_get)
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/transports.py", line 186, in get_response
    status_code, response.text))
socketIO_client.exceptions.ConnectionError: unexpected status code (400 {"code":0,"message":"Transport unknown"})

Trying to force it to websocket initially has been, thus far, unsuccessful. Even with taking xhr-polling out of the transports in the source didn’t quite work, and replacing the polling class with the websocket class (for science) just ended up really pissing it off. (Unsurprisingly.)

I’ll keep poking at it more later and see what I can get. I’m sure there’s a real way to force it to use websocket transport…

Note the transport=polling at the end of this.

>>> sio = SocketIO('https://online-go.comm',wait_for_connection=False,transports=['websocket'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 353, in __init__
    resource, hurry_interval_in_seconds, **kw)
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 54, in __init__
    self._transport
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 62, in _transport
    self._engineIO_session = self._get_engineIO_session()
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/__init__.py", line 76, in _get_engineIO_session
    transport.recv_packet())
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/transports.py", line 82, in recv_packet
    **self._kw_get)
  File "/root/jam/socketio/clean/lib/python2.7/site-packages/socketIO_client/transports.py", line 180, in get_response
    raise ConnectionError(e)
socketIO_client.exceptions.ConnectionError: HTTPSConnectionPool(host='online-go.comm', port=443): Max retries exceeded with url: /socket.io/?EIO=3&transport=polling&t=1494034135266-0 (Caused by NewConnectionError('<requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x7fa4ef825590>: Failed to establish a new connection: [Errno 111] Connection refused',))

Update on where I am now: I have decided to move to a node.js middle man between the OGS server and my python program. The connection between node js and python seems to be working fine (am using zerorpc). The node.js notifies python when it needs to make a move, and then the python program decides what to move, then calls the sendmove function inside of the node.js file. However, I can’t seem to get the node.js file to be able to send moves to the server and have them show up. The only time that I seem to be able to successfully send moves is when listening for a “connect” response. I made a little node.js program that uses that fact, but it only sends one move per socket connection. When I use this to submit moves, the whole system works for 2 moves then the server stops accepting moves. I also think making a new socket for every move is very wasteful and probably hard on the server, so what is the better way to do this?

OGSInterface.js:

'use strict';

let io = require('socket.io-client');
let http = require('http');
let https = require('https');
let crypto = require('crypto');
let request = require('request');


let optimist = require("optimist")
	.usage("Usage: $0 --id <bot id> --auth <api key> --gameid <game id>")
	.demand('id')
	.demand('gameid')
	.demand('auth')
	.describe('id', 'Specify the id of the bot')
	.describe('auth', 'Specify the API key for the bot')
	.describe('gameid', 'Specify the game id to make the move in')
;
let args = optimist.argv;

let game_id = args.gameid;
let id = args.id;
let auth = args.auth;

let zerorpc = require("zerorpc");

let client = new zerorpc.Client();
client.connect("tcp://127.0.0.1:4242");

let socket = let this.socket = io('https://online-go.com:443', {
	reconection: true,
	reconnectionDelay: 500,
	reconnectionDelayMax: 60000,
	transports: ['websocket'],
});

let bot_id = null;
let jwt = null;

socket.on('connect', () => {
	console.log("----OGSinterface.js: Connected.")
	this.socket.emit('net/ping', {client: (new Date()).getTime()});
	this.socket.emit('authenticate', {'auth':auth});
	this.socket.emit('game/connect', {'game_id':game_id, "bot_id":id, "auth":auth})
	this.socket.emit('bot/id', {'id': 'theonly747beast'}, (obj) => {
        bot_id = obj.id;
        jwt = obj.jwt;
        console.log(jwt)
        console.log("Bot is user id:", bot_id);
    });
});

var server = new zerorpc.Server({
	sendmove: function(apikey, gamekey, move, socket, reply) {
		console.log(apikey, gamekey, move, game_id, jwt);
		if (move == "resign") {
			console.log("----sendmove.js: Resigning.");
			this.socket.emit("game/resign", {
				"apikey": apikey,
				"auth": gamekey,
				"game_id": game_id,
				"bot_id": bot_id,
				"player_id": id,
				"jwt": jwt}, () => {});
		} else {
			console.log("----sendmove.js: Sending move", move);
			this.socket.emit('game/move', {
				"apikey": apikey,
				"auth": gamekey,
				"game_id": game_id,
				"player_id": id,
				"bot_id": bot_id,
				"move": move,
				"jwt": jwt
			}, () => {console.log("----sendmove.js: Sent!");});
		}
		reply(null)
	}
});

server.bind("tcp://0.0.0.0:4243");

socket.on('game/' + game_id + '/gamedata', (gamedata) => {
	console.log("----OGSinterface.js: Game Updated.")

            //this.log("Gamedata:", JSON.stringify(gamedata, null, 4));
            this.state = gamedata;
            this.my_color = id == this.state.players.black.id ? "black" : "white";
            this.opponent_evenodd = this.my_color == "black" ? 0 : 1;

            // First handicap is just lower komi, more handicaps may change who is even or odd move #s.
            //
            if (this.state.free_handicap_placement && this.state.handicap > 1) {
                //In Chinese, black makes multiple free moves.
                //
                this.opponent_evenodd = (this.opponent_evenodd + this.state.handicap - 1) % 2;
            } else if (this.state.handicap > 1) {
                // In Japanese, white makes the first move.
                //
                this.opponent_evenodd = this.my_color == "black" ? 1 : 0;
            }

            // If server has issues it might send us a new gamedata packet and not a move event. We could try to
            // check if we're missing a move and send it to bot out of gamadata. For now as a safe fallback just
            // restart the bot by killing it here if another gamedata comes in. There normally should only be one
            // before we process any moves, and makeMove() is where a new Bot is created.
            //

            // active_game isn't handling this for us any more. If it is our move, call makeMove.
            //
            if (this.state.phase == "play" && this.state.clock.current_player == id) {
            	client.invoke("makeMove");
            }
        });

socket.on('game/' + game_id + '/move', (move) => {
	console.log("----OGSinterface.js: game/" + game_id + "/move:", move);
	if (move.move_number % 2 == this.opponent_evenodd) {
        // We just got a move from the opponent, so we can move immediately.
        client.invoke("makeMove");
    } else {
    	console.log("----OGSinterface.js: Ignoring our own move", move.move_number);
    }
});

RandomPlayer.py:

import requests
import time
import websocket
import zerorpc
import random
from sgfmill import sgf, boards
from subprocess import Popen, call


s = requests.Session()
ogs_link = 'http://online-go.com'

home = s.get(ogs_link + '/api')
auth = s.post('https://online-go.com/oauth2/token/', data={"username":"theonly747beast", "password":"NotMyPassword", "client_id":"NotMyId", "client_secret": "NotMySecret", "grant_type": "password"})

oauth2_json = auth.json()
access_token = oauth2_json['access_token']
refresh_token = oauth2_json['refresh_token']

prof = s.get('https://online-go.com/api/v1/me/',
		headers = {
			'Authorization': 'Bearer {}'.format(access_token),
		},
)

def rank_to_display(rank_value):
	if rank_value < 30:
		return '%dk' % (30-rank_value,)
	else:
		return '%dd' % ((rank_value-30)+1)

my_info_json = prof.json()

# print(my_info_json)
# print()
print('overall: {}, blitz: {}, live: {}, corr: {}'.format(
	rank_to_display(my_info_json['ranking']),
	rank_to_display(my_info_json['ranking_blitz']),
	rank_to_display(my_info_json['ranking_live']),
	rank_to_display(my_info_json['ranking_correspondence']),
))
print()

challenge = s.post('https://online-go.com/api/v1/challenges',
		headers = {
			'Authorization': 'Bearer {}'.format(access_token),
			'Content-Type': 'application/json'
		},
		json = {
			'game': {
				'name': 'Test Game -- Please don\'t join!',
				'rules': 'japanese',
				'ranked': False,
				'handicap': 0,
				'time_control': 'simple',
				"time_control_parameters": {
					'time_control': 'simple',
    				"per_move": 30
  				},
				'pause_on_weekends': False,
				'width': 19,
				'height': 19,
				'disable_analysis': False,
			},
			'challenger_color': 'automatic',
			'min_ranking': 0,
			'max_ranking': 10,
		},
		allow_redirects = False,
);

print("Access Token: " + access_token)



class Random(object):
	def makeMove(self):
	    gameinfo = requests.get('https://online-go.com/api/v1/games/' + str(challenge.json()['game']), headers = {'Authorization': 'Bearer {}'.format(access_token)})
	    sgfstr = requests.get('https://online-go.com/api/v1/games/' + str(challenge.json()['game']) + "/sgf/")
	    game = sgf.Sgf_game.from_string(sgfstr.text)
	    board = boards.Board(19)
	    array = []
	    for node in game.get_main_sequence():
	        move = node.get_move()
	        if move[0] == None:
	            continue
	        if move[1] == None:
	            continue
	        try:
	            board.play(move[1][0], move[1][1], move[0])
	        except ValueError:
	            print("Value Error!")
	            return
	        except IndexError:
	            print("Index Error!")
	            return
	    occupied = board.list_occupied_points()
	    possiblemoves = [(i, j) for i in range(19) for j in range(19)]
	    for spot in occupied:
	    	possiblemoves.remove(spot[1])
	    print(possiblemoves)
	    move = random.choice(possiblemoves)
	    letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's']
	    print("Making random move.")
	    print(board)
	    print(move)
	    # call(["node", "sendmove.js", "--id", str(my_info_json['id']), "--gameauth", str(gameinfo.json()['auth']), "--gameid", str(challenge.json()['game']), "--apikey", access_token, "--move", letters[move[0]] + letters[move[1]]])
	    c = zerorpc.Client()
	    c.connect("tcp://127.0.0.1:4243")
	    c.sendmove(access_token, gameinfo.json()['auth'], letters[move[0]] + letters[move[1]])
	    print("Done sending random move.")



s = zerorpc.Server(Random())
s.bind("tcp://0.0.0.0:4242")
Popen(["node", "OGSinterface.js", "--id", str(my_info_json['id']), "--auth", access_token, "--gameid", str(challenge.json()['game'])])
s.run()

sendmove.js:

'use strict';
let optimist = require("optimist")
	.usage("Usage: $0 --id <bot id> --apikey <api key> --gameid <game id> --gameauth <gameauth> --move [ab]")
	.demand('id')
	.demand('move')
	.demand('gameid')
	.demand('apikey')
	.demand('gameauth')
	.describe('id', 'Specify the id of the bot')
	.describe('apikey', 'Specify the API key for the bot')
	.describe('gameauth', 'Specify the game authorization code')
	.describe('gameid', 'Specify the game id to make the move in')
	.describe('move', 'Specify the move for the bot')
;
let args = optimist.argv;

let id = args.id;
let game_id = args.gameid;
let move = args.move;
let apikey = args.apikey;
let gamekey = args.gameauth;

console.log(id, game_id, move, apikey, gamekey);

let io = require('socket.io-client');

let socket = io('https://online-go.com:443', {
	reconnection: true,
	reconnectionDelay: 500,
	reconnectionDelayMax: 60000,
	transports: ['websocket'],
});

socket.on('connect', () => {
	if (move == "resign") {
		console.log("----sendmove.js: Resigning.");
		socket.emit("game/resign", {
			"apikey": apikey,
			"auth": gamekey,
			"game_id": game_id,
			"bot_id": id,
			"player_id": id}, () => {process.exit();});
	} else {
		console.log("----sendmove.js: Sending move", move);
		socket.emit('game/move', {
			"apikey": apikey,
			"auth": gamekey,
			"game_id": game_id,
			"player_id": id,
			"bot_id": id,
			"move": move
		}, () => {console.log("----sendmove.js: Sent!"); process.exit();});
	}
});

Basically, the problem is that the socket hangs on “game/move” and doesn’t ever actually make the move when you make the call outside socket.on("connect", ()=>{...here...});.

Hi there, just wondering if you finally managed to make this work as I am currently faced with the same issue