Integrating Etsy sale alerts to Discord

Discord bot sending an alert for a new sale with a gif of Claude from the Fire Emblem franchise
Hey Claude! 😉

Initial Planning

  1. Poll Etsy’s API for shop sales
  2. When there’s a new sale, post a message to Discord about it
  1. Get my friend to generate an Etsy API key and give it to me so I could develop my app with her data
  2. Request “Full Access”
  3. Scrape the site
  4. Think of something else
My favorite WicksByWerby candle (photo from the WicksByWerby Etsy shop)

General Architecture

System Diagram
  1. Query the Etsy API for our shop’s Listings which gives us the inventory count for each item
  2. Compare the current Listings with the last time we queried’s Listings
  3. For any decrease in inventory, post a message using a Discord webhook
previous_inventory = read_last_inventory()
current_inventory = read_current_inventory_from_etsy()
diffs = compare(previous_inventory, current_inventory)
for diff in diffs:
send_discord_message(diff['name'])
# if the inventory has changed, write out the new one to
# our database
if diff:
write(current_inventory)

Step 1: Querying the Etsy API

  • Query the API for the latest Listings data
  • Transform this data into just a dictionary of the stuff we’re interested in
  • Compare the last inventory data with the current one in order to detect changes in inventory
class Etsy:
api_key = "SECRET ETSY API KEY"
store_name = "wicksbywerby"
def request_listings():
"""Query the etsy shop for the latest listings"""
endpoint = "https://openapi.etsy.com/v2/shops/{self.shop_name}"
response = http_get(endpoint,
params={
"includes": "Listings",
"api_key": api_key
})
return response
def transform_data(listings):
"""Transform raw etsy response data. We are only interested
in items that are active or sold out, and also only
interested in quantities"""
inventory_state = {}
for item in listings():
if item['state'] == 'active' or item['state'] == 'sold_out':
inventory_state[item['listing_id'] = {
"listing_id": item['listing_id'],
"name": item['title'],
"quantity": item['quantity']
}
return inventory_state
def get_inventory_state():
"""Just a combination of the other two functions"""
listings = request_listings()
return transform_data(listings)

Step 2: Comparing Listings

def get_inventory_diff(prev_inventory, cur_inventory):
"""Compare previous and current inventories and return
a dictionary of diffs, keyed by id of the listing. Only
return diffs where inventory has decreased, indicating
a sale."""
diffs = {}
for listing_id in prev_inventory:
name = prev_inventory[listing_id]['name']
prev_quantity = prev_inventory[listing_id]['quantity']
cur_quantity = cur_inventory[listing_id]['quantity']
if prev_quantity > cur_quantity:
diffs[listing_id] = {
'num_sales': cur_quantity - prev_quantity,
'name': name
}
return diffs
class Database:
data_dir = "/data"
inventory_file = "inventory.json"
def write_inventory(inventory):
file_obj = open(inventory_file)
file_obj.write(inventory)
def get_inventory():
file_obj = open(inventory_file)
return file_obj.read()

Step 3: Post to Discord

class Discord:
webhook_url = "DISCORD WEBHOOK URL"

def send_sales_message(message):
"""Post a message to Discord with an image"""
payload = {
"username": "Sale Alerter",
"avatar_url": "link_to_image",
"content": message,
}
http_post(webhook_url, json=payload)

Putting it all together

# get our data
previous_inventory = Database.get_inventory()
current_inventory = Etsy.get_inventory_state()
# compare the data
diffs = get_inventory_diff(previous_inventory, current_inventory)
# send a message for each inventory diff
for listing_id in diffs:
item = diffs[listing_id]
msg = "Shop just sold {item['num_sales']} of {item['name']}!!"
Discord.send_sale_message(msg)
# write out inventory for next run
if diffs:
Database.write_inventory(current_inventory)

Deploying

# crontab# once every minute: 
# 1. go to the directory where our code lives
# 2. activate the Python virtual environment
# 3. run the program and output logs to a file
*/1 * * * * cd /sales-alerter && venv/bin/python3 -m sales.main >> /sales-alerter/logs.txt 2>&1

Limitations

Extra Credit

# listing_images.json{
"listing_id": {
"name": "Zagreus",
"images": [
"https://yayzagreus.fakeurl.com/hotfoot.jpg",
"https://zagreusfanclub.fakeurl.com/pom_of_power.jpg"
]
}
...
}
def get_image_for_item(listing_id):
"""Read in our list of potential images and return one
random URL"""

all_listing_images = read(listing_images.json)
item_image_urls = all_listing_images[listing_id]['images']
return random(item_image_urls)
# get our data
previous_inventory = Database.get_inventory()
current_inventory = Etsy.get_inventory_state()
# compare the data
diffs = get_inventory_diff(previous_inventory, current_inventory)
# send a message for each inventory diff
for listing_id in diffs:
item = diffs[listing_id]
msg = "Shop just sold {item['num_sales']} of {item['name']}!!"
image_url = get_image_for_item(listing_id)
Discord.send_sale_message(msg, image_url)
# write out inventory for next run
if diffs:
Database.write_inventory(current_inventory)
class Discord:
...
def send_sales_message(message, image_url):
"""Post a message to Discord with an image"""
payload = {
"username": "Sale Alerter",
"avatar_url": "link_to_image",
"content": message,
"embeds": {
"image": {"url": image_url}
}

}
New Hades Bundle Sale! Embedded image from the Supergiant Games Twitter account

--

--

--

software engineer | writer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Welcome back, Sara!

The Subtle Art of Debugging Your Code

indigo blue beetle

Data Structures and Algorithms: Digraph Processing

Creating good requirements — a simple guide for Business Analysts

Argo Rollouts v1.1

Best YouTube Channels to Learn Python

Halloween Candy Drop 🎃 #2

Setup a Conjur OSS Environment in Ubuntu 20.04 LTS with Docker Engine

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Allison King

Allison King

software engineer | writer

More from Medium

Get free traffic and backlinks with Linkcollider !

Website: https://www.shovelhub.net/

Making a Discord Bot for Nintendo 3DS

Introducing CreatorClub–the first community-based membership and e-learning platform for…