In this post, we will show you how to use a simple python script to watch your Wow Classic queue and alert you when your queue is nearly up! It uses image recognition to examine screenshots taken periodically while your queue is showing on your screen.
The function needs to do 3 things:
We will walk through the steps to create this function below.
You'll need Python 3.6 or higher: you can find information on downloading and installing python here if needed.
You'll also need the python package manager pip
for installing the libraries. If you need information on pip
you can find it here.
You will need to install the following two packages:
pytesseract: This is an optical character recognition (OCR) package which we will use to extract the queue time information from a screenshot of your WoW classic queue.
To install pytesseract
, first you'll need to install Tesseract which is the underlying engine. After you have Tesseract on your system, you can install the pytesseract
package with the PIP command: pip install pytesseract
pillow: This is an image processing library which we will use to capture and prepare the screenshots. Install it using the PIP command: pip install pillow
We will also use the time
, smtplib
, re
and email
libraries that ship with python. You don't need to do anything special for these, just be sure to import
them before using them.
If you'd like to play a song from your computer as an alarm, you'll need to download playsound with pip install playsound
.
Now, onto the fun part. We need to periodically capture an image of the current WoW Classic queue, and extract the information we want: the minutes left in the queue - i.e. the "199" below (full image not shown).
We will use pillow's ImageGrab.grab()
to screenshot every 5 seconds, and put the screenshot image in a variable called "img". The while True
in line 2 means it will run indefinitely until it is told to stop.
- from PIL import ImageGrab
- while True:
- time.sleep(5)
- img = ImageGrab.grab()
Next we need to process our screenshot ("img") to prepare it for image recognition. This code will crop the image (line 3), apply a threshold to increase contrast (line 4), remove the color (line 5) so that the image becomes black and white.
- from PIL import ImageEnhance
- width, height = img.size
- img = img.crop((width*.3, height*.4, width*.7, height*.6))
- img = img.point(lambda p: p > 128 and 255)
- img = ImageEnhance.Color(img).enhance(0)
This processing makes it easier for the text recognition by removing shadows, gradiants and colors.
Now the image is ready we use pytesseract's image_to_data
function to extract the data from the processed image. The image_to_data
function returns a dictionary that includes the text, its location, and a confidence value.
We'll use a list comprehension (line 3) to filter to the text elements that have a reasonable degree of confidence (rendered in green below), then join those elements into a single string (line 3).
Finally we'll use regular expressions (regex) extract the numeric digits after the word "time", so that we can check how long is left in the queue.
- import pytesseract
- import re
- imgdata = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
- imgtext = ' '.join([x for i,x in enumerate(imgdata['text']) if int(imgdata['conf'][i]) >= 70])
- imgtime = re.search(r'time[^\d+]*(\d+)', imgtext).group(1)
We will wrap the image processing steps into a single get_time_in_queue()
function. This function will accept the screenshot image as a parameter called img
, process it, and return the queue minutes as an integer, or a None
value if it is unable to extract the data.
- def get_time_in_queue(img):
- width, height = img.size
- img = img.crop((width*.3, height*.4, width*.7, height*.6))
- img = img.point(lambda p: p > 128 and 255)
- img = ImageEnhance.Color(img).enhance(0)
- imgdata = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
- imgtext = ' '.join([x for i,x in enumerate(imgdata['text']) if int(imgdata['conf'][i]) >= 70])
- imgtime = re.search(r'time[^\d+]*(\d+)', imgtext)
- if imgtime:
- return int(imgtime.group(1))
Ths simplest option is to sound a system alert. Here is a super simple way of playing a system alert tone on most systems:
- print(chr(7))
This may not work in an integrated development environment or notebook, but it should work in your python console or in a python script executed from the console.
Recieving an email when the queue is ready can be convenient, especially if you get email notifications on your phone, and you aren't at your computer.
To do so, we need a few of Python's in-built libraries, and to configure the email login and send settings. We won't go into a huge amount of detail on setting up email notifications as there are plenty of resources out there already.
The following imports come packaged up with Python so all you need to do is add them to the list of imports at the top of your file.
- import smtplib
- from email.mime.multipart import MIMEMultipart
- from email.mime.text import MIMEText
The code to set up an email server is shown below, but don't forget to substitute your email provider's details. If you are using one of Microsoft's services, you're most likely good to go with the below.
- emailserver = smtplib.SMTP(host='smtp-mail.outlook.com', port=587)
- emailserver.starttls()
- emailserver.login(EMAILADDRESS, PASSWORD)
NB: You will note that we haven't coded USERNAME or PASSWORD. These values are normally passed in as strings. We do NOT recommend hardcoding any credentials directly into a Python script! This is especially important if you are using version control that pushes to the cloud, for e.g. Github.
These credentials should be, at the very least, saved in another file and have some basic encryption.
One option is to save your details (e.g. as a class called outlook
containing email
and password
attributes) as a python module, encrypt it, and import it like from mypymodule import outlook
. You can then replace all instances of USERNAME and PASSWORD with outlook.email
and outlook.password
. Be careful though, anyone with a login to your computer can still potentially access your details.
Once the server is set up, we can create a message object, add some contents, and then use that emailserver
to send the message.
- msg = MIMEMultipart()
- msg['From'] = EMAILADDRESS
- msg['To'] = EMAILADDRESS
- msg['Subject'] = "Time to WoW!"
- message = 'Your WoW queue is about to pop!'
- msg.attach(MIMEText(message, 'plain'))
- emailserver.send_message(msg)
And just because we can, here's how you can play a song from your computer for an extra special alarm. You'll probably want to update the file path to something a bit more interesting than this windows alarm!
- songfile = 'C:\\Windows\\Media\\Alarm01.wav'
- from playsound import playsound
- playsound(songfile)
Again, we will wrap this into a function so that we can easily turn alerts on or off.
We will include a named argument for whether or not to play an alarm sound (with a default value alarm=True
), as well as whether to send an email (with a default value email=True
).
The full send alert function:
- def send_alert(alarm=True, email=False, playsong=False):
- if alarm:
- print(chr(7))
-
- if email:
- # set up the SMTP server
- emailserver = smtplib.SMTP(host='smtp-mail.outlook.com', port=587)
- emailserver.starttls()
- emailserver.login(EMAILADDRESS, PASSWORD)
- # set up the message
- msg = MIMEMultipart()
- msg['From'] = EMAILADDRESS
- msg['To'] = EMAILADDRESS
- msg['Subject'] = "Time to WoW!"
- message = 'Your WoW queue is about to pop!'
- msg.attach(MIMEText(message, 'plain'))
- # send the message
- emailserver.send_message(msg)
- print(f'Email notification sent to EMAILADDRESS')
-
- if playsong:
- songfile = 'C:\\Windows\\Media\\Alarm01.wav'
- from playsound import playsound
- playsound(songfile)
The last step is to pull it all together. We will create an overarching function queue_alert()
that periodically takes a screenshot, uses our get_time_in_queue()
function to extract the test and then calls our send_alert()
function if the queue is almost up.
- import time
- def queue_alert(alertat=2, interval=6, occurences=3, alarm=True, emailalert = False, playsong = False):
- counter = 0
- while True:
- # capture screenshot of the current queue
- time.sleep(interval)
- img = ImageGrab.grab()
- # extract the queue time
- minutes = get_time_in_queue(img)
- # alert when queue reaches threshold
- if not minutes:
- print('Could not extract queue')
- else:
- print(f'Current queue: {minutes}')
- if minutes <= alertat:
- counter += 1
- if counter >= occurences:
- print('Your queue is about to pop!')
- send_alert(alarm=alarm, email=emailalert, playsong=playsong)
- emailalert = False
- # stop after 15 alarms
- if counter > 15:
- break
To run this on your own computer, you can download the final script from the link below.
download full scriptUse a text editor or IDE to make any changes you desire, such as adding your email details, selecting a song to play from disk, or adjusting which alerts are used (in line 97).
Run the script and then leave the WOW queue up on the screen with python in the background - it's important that the queue is unobstructed on the screen as this is how python will track it.
Python will take a screenshot every few seconds, process it, and let you know when the queue is ready.
Congratulations, you've beaten the queue!
If you have any questions, just shout in the comments below. And let us know any bright ideas about other uses for this hack!
Our blog comments aren't up and running just yet, but you can hit us up on YouTube while we're working on it.
Happying WoWing!