A CLI for CSGO, another useless side-project?
To be honest, I don’t think there is any use case for a CLI (Command Line Interface) for the game CSGO (Counter Strike Global Offensive). But who cares? For me, a side-project does not have to have any use case or so. For me, a side-project is about learning and trying new stuff. I learned how easy it is to write a CLI in Python, using typer, and I realized how great Python is for automating stuff.
In this blog post, which is my first blog post ever, I will go through how I created a CLI for CSGO. All the code shown in this blog and the source code for the project is available on github, and the CLI is available PyPi*.
** The code shown here in the blog is functional but simplified, for edge-case support, exception handling, and more robust code, I suggest having a look at the source code on github.*
What should the CLI do?
From the start, the idea was that the CLI should only contain two simple commands.
- servers: Display live information like the number of players and current map for my favorite marked servers.
- connect: Automatically start CSGO and connect to a given server.
The workflow I had in mind was that I should quickly get an overview of my favorite servers and connect to one. The goal was that I should not have to interact with CSGO myself before I joined a server.
Getting Started with the Developing
When working with Python, I prefer working in virtual environments.
I start by:
$ python3 -m venv venv
# On Windows
$ ./venv/Scripts/activate
# On Unix
$ source venv/bin/activate
I knew that I would require the typer package, and I installed it by
$ pip install typer
Installing collected packages: click, typer
Successfully installed click-7.1.2 typer-0.3.2
The Typer package is very intuitive and easy to work with. On a high level, you first create a Typer object which you then add commands to. In Typer, commands are to decorated python functions. Below is some boilerplate code for csgo.py
.
import typer
app = typer.Typer()
@app.command()
def servers():
typer.echo("Listing my favorite servers...")
@app.command()
def connect():
typer.echo("Connecting to a server...")
if __name__ == "__main__":
app()
To run the CLI defined in csgo.py
:
$ python csgo.py
Usage: post.py [OPTIONS] COMMAND [ARGS]...
Options
--help Show this message and exit.
Commands:
connect
servers
And to execute a specific command:
$ python csgo.py connect
Connection to a server...
At the end, I explain how to set up the CLI so that you can run it like $ csgo servers
and not $ python csgo.py servers
Let’s add the proper functionality to the commands!
Steam stores the favorite marked servers in a file called serverbrowser_hist.vdf
, and I found it here:
/c/Program Files (x86)/Steam/userdata/160616678/7/remote/serverbrowser_hist.vdf
. This file will be necessary for both commands but let’s start implementing the servers command.
Command: servers
In python, there are packages for everything! I use the vdf for reading the server serverbrowser_hist.vdf
into a dict, and I use python a2s for querying information from the servers. The servers
function looks like this:
@app.command()
def servers():
"""
List your favorite GSGO servers
"""
servers = get_servers()
# Print out a nice table with the live information about the servers
table = tabulate(servers, headers="keys")
typer.echo(f"{table}\n")
def get_servers():
# Read the serverbrowser_hist.vdf
# PATH is is the path to the serverbrowser_hist.vdf file.
serverbrowser = vdf.load(open(PATH))
favorite_servers = serverbrowser['Filters']['Favorites']
server_infos = []
for server in favorite_servers:
# Extract server information
server = favorite_servers[server]
server_ip = server['address'].split(':')[0]
server_port = int(server['address'].split(':')[1])
address = (server_ip, server_port)
try:
# Try to query live server information
info = a2s.info(
address,
timeout=a2s.defaults.DEFAULT_TIMEOUT,
encoding=a2s.defaults.DEFAULT_ENCODING
)
server_info = {
"INDEX": len(server_infos),
"NAME": info.server_name,
"MAP": info.map_name,
"PLAYERS": f"{info.player_count}/{info.max_players}",
"ADDRESS": f"{server_ip}:{server_port}",
}
server_infos.append(server_info)
except socket.timeout:
pass
Running this command results in the following output:
$ python csgo.py servers
INDEX NAME MAP PLAYERS ADDRESS
------- -------------------------------------------------------------------------- -------------- --------- -------------------
0 BrutalCS - RETAKES ★ 02 ★ MAPVOTE ★ 128T de_cbble 10/10 178.236.67.18:27015
1 BrutalCS - RETAKES ★ 04 ★ MAPVOTE ★ 128T de_inferno 10/10 178.236.67.33:27015
2 BrutalCS - RETAKES ★ 13 ★ MI/CA/INF ★ 128T de_mirage 1/12 178.236.67.56:27015
3 BrutalCS - RETAKES ★ 14 ★ MAPVOTE ★ 128T de_dust2 10/10 178.236.67.20:27015
4 BrutalCS - RETAKES ★ 12 ★ MI/CA/INF ★ 128T de_inferno 1/12 178.236.67.55:27015
5 BrutalCS - RETAKES ★ 07 ★ MAPVOTE ★ 128T de_vertigo 1/12 178.236.67.37:27015
Now that I can list my favorite servers from a simple CLI command, I just need to be able to connect to one of them.
Command: connect
A simple way to connect to a steam server from python is to use steams browser protocol. For instance, by entering steam://connect/<ip>
where ip
is the IP to a CSGO server, two things will happen. First, CSGO gets started, and secondly, it will connect to the server automatically. To take advantage of this steam browser protocol, I used the webbrowser python module.
This command needs to take an argument, namely, the server in the list above to connect to. Arguments for a command are created by typer.Argument(...)
see the connect
command below for an example.
@app.command()
def connect(
server_index: int = typer.Argument(None):
"""
Start CSGO and connect to a specific game server.
"""
server = get_servers()[server_index]
typer.echo(f"Starting CSGO and joining server {server['NAME']} ...")
server_url = server['ADDRESS']
webbrowser.open(f'steam://connect/{server_url}')
return
I can now use this connect
command for connecting to a server, for instance, server number one in the table above.
$ python csgo.py connect 1
Starting CSGO and joining server BrutalCS - RETAKES ★ 04 ★ MAPVOTE ★ 128T
CSGO gets started and automatically connects to BrutalCS - RETAKES ★ 04 ★ MAPVOTE ★ 128T
, the project succeeded.
Good Times!
Simplify the usage of the CLI
Now that I’m happy with the CLI, the last thing to do is simplify its usage. I don’t want to have to type $ python csgo.py
for triggering the CLI. Let’s remove that part!
Create setup.py
that requires the setuptools
package.
import setuptools
with open("requirements.txt") as f:
required = f.read().splitlines()
setuptools.setup(
name="csgo-cli",
version="0.1.0",
packages=setuptools.find_packages(),
python_requires=">=3.6",
install_requires=required,
entry_points={
'console_scripts': ['csgo=cli:app'],
}
)
This setup.py
requires a requirements.txt
file and the content of that file:
typer
setuptools
tabulate
python-a2s
vdf
typer
setuptools
The setup.py
allows easy install of the Python Package. Using a setup.py
module is the standard way for distributing Python Modules. To install this CSGO CLI Package locally, I run the following pip
command:
$ pip install -e .
Now I’m happy! The CLI is finished, and I can use it by typing csgo
in the terminal!
Thanks for reading!