Skip to content

Instantly share code, notes, and snippets.

@dreness
Last active December 21, 2015 04:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dreness/6247320 to your computer and use it in GitHub Desktop.
Save dreness/6247320 to your computer and use it in GitHub Desktop.
Send email notifications of shares and blocks.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import time, os
import re
from email.mime.text import MIMEText
from subprocess import Popen, PIPE
from datetime import datetime, timedelta
# Watch the bitcoin log, send notifications of shares and blocks, with
# current payout
### SETTINGS
# The bitcoin log to watch for interesting events
BTLOG = "/home/towelie/.bitcoin/p2pool/data/bitcoin/log"
# Log file to update with status and sent message notifications
ACTIVITY_LOG = "/home/towelie/watcher.log"
# The address to notify. If SMS, consider setting TERSE_OUTPUT to True below.
EMADDRESS = "towelie@example.com"
# Set a custom 'full name' email field. Doesn't seem to work with SMS.
# This is used as the argument for sendmail's -F option
EMAIL_FROM = "P2Pool"
# In terse mode, we use no message subject and a shorter body
TERSE_OUTPUT = True
#TERSE_OUTPUT = False
# Max number of messages we'll send within 1 minute. If we go over, we assume
# something went crazy and we exit.
MAX_PER_MINUTE = 10
# Enable or disable test mode. In test mode, we'll process the log
# starting from the beginning, and send notifications until we hit the
# sanity check threshold, or until we reach the end of the file. This tests
# both delivery and possibly the sanity check.
TEST_MODE = False
#TEST_MODE = True
# Customize the message prefix. Emojo might not work with SMS.
#MESSAGE_PREFIX = "💰 "
#MESSAGE_PREFIX = "btc-watcher: GOT"
MESSAGE_PREFIX = ""
###
# The regular expression pattern for parsing the current payout
PAYOUT_RE = '.* Current payout: (\S+) BTC'
# Store the current payout rate here (it's a float)
PAYOUT = 0.0
# Full status here
STATUS = ''
# Keep track of times we've sent email
SENT = []
# open the log file, get its size
file = open(BTLOG, 'r')
st_results = os.stat(BTLOG)
st_size = st_results[6]
# If we're not in testmode, seek to the end of the file
if not TEST_MODE:
file.seek(st_size)
# Also open our activity log for writing
activity = open(ACTIVITY_LOG,'a')
# The adjusted payout doesn't arrive in the log until after SHARE / BLOCKs
# are logged. Every time we get a share / block, we'll wait until the next
# payout rate arrives before sending the message.
pendingMessage = ''
### Functions
# Sanity check; have we sent more than MAX_PER_MINUTE in the last minute?
def overloaded():
global SENT
global MAX_PER_MINUTE
inRange = 0
now = datetime.now()
d = timedelta(minutes=1)
for x in SENT:
if x > now - d:
inRange += 1
if inRange > MAX_PER_MINUTE:
return True
else:
return False
# Compose and send a message
def sendMessage(line, PAYOUT, STATUS):
global SENT, pendingMessage, MESSAGE_PREFIX
global TERSE_OUTPUT, EMADDRESS, activity
# Compose the message. Emoji doesn't seem to work via (some?) SMS
subject = MESSAGE_PREFIX
# Add the type of event and maybe some details
if ('SHARE' in line):
subject += "SHARE! [%.4f BTC]" % (PAYOUT,)
if ('BLOCK' in line):
subject += "BLOCK! [%.4f BTC]" % (PAYOUT,)
if not TERSE_OUTPUT:
msg = MIMEText("%s\n%s" % (line,STATUS))
msg["Subject"] = subject
else:
msg = MIMEText("%s\n" % (subject,))
msg["To"] = EMADDRESS
aLog("%s sending: %s to address %s\n" %
(stamp,subject,EMADDRESS))
if overloaded(): # sanity check
print "exiting due to insane message rate!"
aLog("%s bailed due to insane message rate!\n" % (stamp,))
exit(1)
# shell out to sendmail, passing the message on stdin
cmd = ['/usr/sbin/sendmail', '-F', EMAIL_FROM, '-t']
aLog("%s\n" % (' '.join(cmd)))
p = Popen(cmd, stdin=PIPE)
p.communicate(msg.as_string())
if p.returncode: # non-zero return code indicates error
aLog("sendmail exited abnormally, bailing! code %r" % (p.returncode))
# Turn off the pendingMessage indicator
pendingMessage = ''
# Store a current timestamp, for rate-limiting sanity checks
SENT.append(datetime.now())
# Truncate the list of timestamps so it doens't grow without bound
# Remove from the front of the list, since new ones appear at the end
if len(SENT) > 50:
SENT = SENT[20:]
def aLog(s):
global activity
activity.write(s)
activity.flush()
### Main
# Loop forever
while 1:
# get the current offset in the file (i.e. current location)
where = file.tell()
# get a line
line = file.readline()
# Make a timestamp for our activity log
stamp = time.strftime("%m-%d-%Y %H:%M:%S", time.localtime())
# if we got nothing, sleep for a bit, reset position, exit the loop early
if (not line):
aLog("%s Current payout: %f\n" % (stamp,PAYOUT,))
time.sleep(10)
file.seek(where)
continue
# We got a line. Process it.
# Get rid of the time / date stamp (first two fields)
line = ' '.join(line.split()[2:])
# Keep track of current status and payout info
if ('Current' in line):
m = re.search(PAYOUT_RE, line)
if (m):
PAYOUT = float(m.group(1))
STATUS = line
if (pendingMessage != ''):
sendMessage(pendingMessage, PAYOUT, STATUS)
# Something cool happened!
if ('GOT' in line):
pendingMessage = line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment