Skip to content

Instantly share code, notes, and snippets.

@fschiettecatte
Last active December 21, 2020 16:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fschiettecatte/32bb21527645312606aa7ec98b5d7048 to your computer and use it in GitHub Desktop.
Save fschiettecatte/32bb21527645312606aa7ec98b5d7048 to your computer and use it in GitHub Desktop.
SwiftBar plugin to display a website status in the macOS menu bar
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#--------------------------------------------------------------------------
#
# Author: Francois Schiettecatte (FS Consulting LLC.)
# Creation Date: October 10, 2020
#
#--------------------------------------------------------------------------
#
# Description:
#
# BitBar plugin to display a website status in the menu bar:
#
# https://github.com/matryer/bitbar
#
# This code assumes that the website has a status URL that returns a JSON
# object that contains a 'status' key with an integer value of 200,
# for example:
#
# { "status": 200 }
#
# Obviously you should adapt this to your own sites.
#
#--------------------------------------------------------------------------
#
# Imported modules
#
import copy
import json
import socket
import sys
import time
import urllib.error
import urllib.request
# Use this monkey patch around SSL certificate errors:
# [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
#--------------------------------------------------------------------------
#
# Constants
#
# Site dict to check, expects JSON object with {'status': 200}
SITES_LIST = [
# Development Website
# {
# 'name': 'Development Website',
# 'url': 'http://website.internal:8000/status',
# },
# Staging Website
{
'name': 'Staging Website',
'url': 'http://staging.website.internal/status',
},
# Production Website
{
'name': 'Production Website',
'url': 'https://website.net/status',
},
]
# Connection timeout
CONNECTION_TIMEOUT = 60
# Connection attempts
CONNECTION_ATTEMPTS = 5
# Connection interval delay (seconds)
CONNECTION_INTERVAL_DELAY = 1
#--------------------------------------------------------------------------
#
# Method: checkSite
#
# Purpose: check site
#
# Parameters: url the url
#
# Exceptions: ValueError Invalid url
#
# Return: (True, None,) for ok, (False, message,) if not
#
def checkSite(url):
# Check parameters
if not url:
raise ValueError('Invalid url')
# Content, content dict, status, request time, exception and error, declared here because we need it later
content = None
contentDict = None
status = None
savedException = None
savedError = None
# Loop for the number of attempts
for attempt in range(0, CONNECTION_ATTEMPTS):
# Sleep between attempts
if attempt > 0:
time.sleep(CONNECTION_INTERVAL_DELAY)
# Clear the content, content dict, status, request time, exception and error
content = None
contentDict = None
status = None
savedException = None
savedError = None
# Set the start time
startTime = int(round(time.time() * 1000))
# Wrap all in a 'try' to catch exceptions
try:
# Open the URL
response = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
# Save the status and read the content
status = response.status
content = response.read()
# Decode the content and parse the JSON string
contentDict = json.loads(content.decode())
# Check the top level status on the content dict,
# we already know that the request status is 200
if contentDict and contentDict.get('status') == 200:
return True, None
# HTTP error
except urllib.error.HTTPError as exception:
savedException = exception
status = exception.code
# URL error
except urllib.error.URLError as exception:
savedException = exception
# JSON decode error
except json.JSONDecodeError as exception:
savedException = exception
# Socket error
except (socket.error, socket.timeout) as error:
savedError = error
# Set the message
message = None
if savedError:
message = 'error: \'{0}\''.format(savedError)
elif savedException:
message = 'exception: \'{0}\''.format(savedException)
elif contentDict:
message = 'site status: \'{0}\''.format(contentDict.get('status'))
elif status:
message = 'request status: \'{0}\''.format(status)
elif not contentDict:
message = 'missing/invalid content'
return False, message
#--------------------------------------------------------------------------
#
# Method: checkSites
#
# Purpose: check sites
#
# Parameters:
#
# Exceptions:
#
# Return:
#
def checkSites():
# Up list, copy of site dict
upSiteDictList = list()
# Down list, copy of site dict
downSiteDictList = list()
# Check each site
for siteDict in SITES_LIST:
# Make a copy of the site dict
siteDict = copy.deepcopy(siteDict)
# Check the site
status, message = checkSite(siteDict['url'])
# Add site dict & message to the appropriate list
if status:
upSiteDictList.append(siteDict)
else:
siteDict['message'] = message
downSiteDictList.append(siteDict)
# Add down site list to rotate in the menu bar
if downSiteDictList:
for downSiteDict in downSiteDictList:
print('🆘 {0}'.format(downSiteDict['name']))
else:
print('✅')
# Separator
print('---')
# Down site list
for name, url, message in downSiteDictList:
print('🆘 {0} ({1}) | href={2}'.format(downSiteDict['name'], downSiteDict.get('message', 'N/A'), downSiteDict['url']))
# Up site list
for upSiteDict in upSiteDictList:
if downSiteDictList:
print('✅', end='')
print('{0} | href={1}'.format(upSiteDict['name'], upSiteDict['url']))
#--------------------------------------------------------------------------
#
# Method: __main__
#
# Purpose: __main__
#
# Parameters:
#
# Exceptions:
#
# Returns:
#
if __name__ == '__main__':
# Check sites
checkSites()
# And exit
sys.exit(0)
#--------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment