WoW Classic: Beat the queue!


WoW queues are a pain, but even worse is missing your queue altogether!

With python your computer can watch your queue for you...
Introduction

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:

  1. Periodically capture a screenshot of the current queue
  2. Extract the queue time remaining from the screenshot
  3. Send an alert when the queue reaches a certain time

We will walk through the steps to create this function below.

Setup
Prerequisites

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.

Libraries

You will need to install the following two packages:

  1. 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

  2. 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 .

Image capture and recognition

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).

Capture a screenshot

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.

  1. from PIL import ImageGrab
  2. while True:
  3. time.sleep(5)
  4. img = ImageGrab.grab()

Process the image

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.

  1. from PIL import ImageEnhance
  2. width, height = img.size
  3. img = img.crop((width*.3, height*.4, width*.7, height*.6))
  4. img = img.point(lambda p: p > 128 and 255)
  5. img = ImageEnhance.Color(img).enhance(0)

This processing makes it easier for the text recognition by removing shadows, gradiants and colors.


Extract the data

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.

  1. import pytesseract
  2. import re
  3. imgdata = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
  4. imgtext = ' '.join([x for i,x in enumerate(imgdata['text']) if int(imgdata['conf'][i]) >= 70])
  5. imgtime = re.search(r'time[^\d+]*(\d+)', imgtext).group(1)

The image processing function

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.

  1. def get_time_in_queue(img):
  2. width, height = img.size
  3. img = img.crop((width*.3, height*.4, width*.7, height*.6))
  4. img = img.point(lambda p: p > 128 and 255)
  5. img = ImageEnhance.Color(img).enhance(0)
  6. imgdata = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
  7. imgtext = ' '.join([x for i,x in enumerate(imgdata['text']) if int(imgdata['conf'][i]) >= 70])
  8. imgtime = re.search(r'time[^\d+]*(\d+)', imgtext)
  9. if imgtime:
  10. return int(imgtime.group(1))
Sending alerts
System alert tone

Ths simplest option is to sound a system alert. Here is a super simple way of playing a system alert tone on most systems:

  1. 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.

Email notification

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.

  1. import smtplib
  2. from email.mime.multipart import MIMEMultipart
  3. 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.

  1. emailserver = smtplib.SMTP(host='smtp-mail.outlook.com', port=587)
  2. emailserver.starttls()
  3. 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.

  1. msg = MIMEMultipart()
  2. msg['From'] = EMAILADDRESS
  3. msg['To'] = EMAILADDRESS
  4. msg['Subject'] = "Time to WoW!"
  5. message = 'Your WoW queue is about to pop!'
  6. msg.attach(MIMEText(message, 'plain'))
  7. emailserver.send_message(msg)
Play a song

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!

  1. songfile = 'C:\\Windows\\Media\\Alarm01.wav'
  2. from playsound import playsound
  3. playsound(songfile)
The send alert function

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:

  1. def send_alert(alarm=True, email=False, playsong=False):
  2. if alarm:
  3. print(chr(7))
  4. if email:
  5. # set up the SMTP server
  6. emailserver = smtplib.SMTP(host='smtp-mail.outlook.com', port=587)
  7. emailserver.starttls()
  8. emailserver.login(EMAILADDRESS, PASSWORD)
  9. # set up the message
  10. msg = MIMEMultipart()
  11. msg['From'] = EMAILADDRESS
  12. msg['To'] = EMAILADDRESS
  13. msg['Subject'] = "Time to WoW!"
  14. message = 'Your WoW queue is about to pop!'
  15. msg.attach(MIMEText(message, 'plain'))
  16. # send the message
  17. emailserver.send_message(msg)
  18. print(f'Email notification sent to EMAILADDRESS')
  19. if playsong:
  20. songfile = 'C:\\Windows\\Media\\Alarm01.wav'
  21. from playsound import playsound
  22. playsound(songfile)
Final script

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.

  1. import time
  2. def queue_alert(alertat=2, interval=6, occurences=3, alarm=True, emailalert = False, playsong = False):
  3. counter = 0
  4. while True:
  5. # capture screenshot of the current queue
  6. time.sleep(interval)
  7. img = ImageGrab.grab()
  8. # extract the queue time
  9. minutes = get_time_in_queue(img)
  10. # alert when queue reaches threshold
  11. if not minutes:
  12. print('Could not extract queue')
  13. else:
  14. print(f'Current queue: {minutes}')
  15. if minutes <= alertat:
  16. counter += 1
  17. if counter >= occurences:
  18. print('Your queue is about to pop!')
  19. send_alert(alarm=alarm, email=emailalert, playsong=playsong)
  20. emailalert = False
  21. # stop after 15 alarms
  22. if counter > 15:
  23. break
In this function, we wait until we have seen the target queue time 3 times before sending alerts. The reason for this is that the queue times can sometimes bounce around, so we want to see sone consistency before we call it.

To run this on your own computer, you can download the final script from the link below.

download full script

Use 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.

Resources
Resource links, including tools used in the video:
  • Jupyter Notebook
    Jupyter notebooks are a great way running code interactively in your browser. You can run anything you can run in your local python, and the packages work the same, but instead of running a script as a whole you're able to step through cell-by-cell and inspect the results - really useful when exploring something new. Check it out here.
  • Sublime Text
    Sublime text is a beautiful text editor which makes writing code easier. Find out more here.
  • Tesseract
    Tesseract is the image recognition brains behind this function. Find out more here.
Summary

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!

WoW
WoWClassic
python
coding
p2w
programmingtowin