From da94864aaa81678cb1660b70385e2e393addc151 Mon Sep 17 00:00:00 2001 From: Robert Hafner Date: Sun, 25 Jun 2017 02:27:49 -0700 Subject: [PATCH 1/2] Add "group" support, where each group can have different recipients This update pushes the system from using a single http and sms endpoint to using any number of endpoints. Messages can be tagged with a one or more groups, and each group can have one or more endpoints associated with it. This way users can send military or defensive alerts to their alliance without spamming them with messages about local economic conditions. --- .settings.dist.yaml | 67 +++++++++-- bin/screepsnotify.sh | 1 - js/Notify.js | 20 ++-- js/notify.d.ts | 2 +- screeps_notify/notify.py | 111 ++++-------------- screeps_notify/services/__init__.py | 0 screeps_notify/services/config.py | 27 +++++ screeps_notify/services/messenger.py | 33 ++++++ .../services/messengers/__init__.py | 0 screeps_notify/services/messengers/http.py | 34 ++++++ screeps_notify/services/messengers/slack.py | 32 +++++ screeps_notify/services/messengers/sms.py | 27 +++++ 12 files changed, 247 insertions(+), 107 deletions(-) create mode 100644 screeps_notify/services/__init__.py create mode 100644 screeps_notify/services/config.py create mode 100644 screeps_notify/services/messenger.py create mode 100644 screeps_notify/services/messengers/__init__.py create mode 100644 screeps_notify/services/messengers/http.py create mode 100644 screeps_notify/services/messengers/slack.py create mode 100644 screeps_notify/services/messengers/sms.py diff --git a/.settings.dist.yaml b/.settings.dist.yaml index b1f2a01..d180957 100644 --- a/.settings.dist.yaml +++ b/.settings.dist.yaml @@ -5,14 +5,65 @@ screeps_username: screeps_password: screeps_ptr: false -# Your Account SID from www.twilio.com/console -twilio_sid: +services: -# Your Auth Token from www.twilio.com/console -twilio_token: + sms: + # Set driver to twilio + type: sms -# You SMS number from twilio. https://www.twilio.com/console/phone-numbers/dashboard -sms_from: '+15555555555' + # Your Account SID from www.twilio.com/console + twilio_sid: -# This should be the number you want to receive the texts. -sms_to: '+15555555555' + # Your Auth Token from www.twilio.com/console + twilio_token: + + # You SMS number from twilio. https://www.twilio.com/console/phone-numbers/dashboard + sms_from: '+15555555555' + + # This should be the number you want to receive the texts. + sms_to: '+15555555555' + + alliance: + # Set driver to HTTP + type: http + + # Specify a url + url: + + # Provide an API key for AWS Gateway (optional) + api-key: + + logs: + # Set driver to HTTP + type: http + + # Specify a url + url: + + # Provide a username for basic http authentication + http_user: + + # Provide a password for basic http authentication + http_password: + + + slack: + + # Set driver to slack + type: slack + + # Get webhook from slack. + webhook_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' + + +groups: + + default: + - sms + - logs + + economy: + - logs + - slack + + defense: all diff --git a/bin/screepsnotify.sh b/bin/screepsnotify.sh index 4874735..0c66b02 100755 --- a/bin/screepsnotify.sh +++ b/bin/screepsnotify.sh @@ -13,7 +13,6 @@ then else DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" fi -cd $DIR/.. ENV="$DIR/../env/bin/activate" if [ ! -f $ENV ]; then diff --git a/js/Notify.js b/js/Notify.js index bd83759..8fa1a76 100644 --- a/js/Notify.js +++ b/js/Notify.js @@ -1,5 +1,9 @@ -var Notify = function (message, limit) { +var Notify = function (message, limit=false, groups=false) { + + if(!groups) { + groups = ['default'] + } // If no limit then send immediately (and potentially repeatedly) if(!limit) { @@ -9,13 +13,15 @@ var Notify = function (message, limit) { // In cases where there are limits we have to record the history. + var queue_message = message + '::' + groups.join('_') + if(!Memory.__notify_history) { Memory.__notify_history = {} } // If the message was sent in the last LIMIT ticks then don't send again. - if(!!Memory.__notify_history[message]) { - var lastSent = Memory.__notify_history[message] + if(!!Memory.__notify_history[queue_message]) { + var lastSent = Memory.__notify_history[queue_message] if(lastSent >= Game.time - limit) { return } else { @@ -25,18 +31,19 @@ var Notify = function (message, limit) { } // Record message in history and send it. - Memory.__notify_history[message] = Game.time - Notify.queueMessage(message) + Memory.__notify_history[queue_message] = Game.time + Notify.queueMessage(message, groups) return 0 } -Notify.queueMessage = function (message) { +Notify.queueMessage = function (message, groups) { if(!Memory.__notify) { Memory.__notify = [] } Memory.__notify.push({ 'message': message, + 'groups': groups, 'tick': Game.time }) } @@ -60,5 +67,4 @@ Notify.cleanHistory = function (limit) { } } - module.exports = Notify diff --git a/js/notify.d.ts b/js/notify.d.ts index b3bc700..9902d38 100644 --- a/js/notify.d.ts +++ b/js/notify.d.ts @@ -1,4 +1,4 @@ declare module "notify" { - function Notify(message: string, limit?: number): void; + function Notify(message: string, limit?: number, groups?: string[]): void; export = Notify; } diff --git a/screeps_notify/notify.py b/screeps_notify/notify.py index 0f132c2..1b7fd95 100755 --- a/screeps_notify/notify.py +++ b/screeps_notify/notify.py @@ -1,37 +1,22 @@ #!/usr/bin/env python -import requests -from screeps import ScreepsConnection +import screepsapi import sys import time -from twilio.rest import TwilioRestClient + import logging import os +import traceback import yaml -base_directory = os.path.expanduser('~') -if not os.path.exists(base_directory): - os.makedirs(base_directory) - - -def getSettings(): - if not getSettings.settings: - cwd = os.getcwd() - path = cwd + '/.settings.yaml' - if not os.path.isfile(path): - print 'no settings file found' - sys.exit(-1) - return False - with open(path, 'r') as f: - getSettings.settings = yaml.load(f) - return getSettings.settings -getSettings.settings = False +import services.config as config +import services.messenger as messenger def getScreepsConnection(): if not getScreepsConnection.sconn: - settings = getSettings() - getScreepsConnection.sconn = ScreepsConnection( + settings = config.getSettings() + getScreepsConnection.sconn = screepsapi.API( u=settings['screeps_username'], p=settings['screeps_password'], ptr=settings['screeps_ptr']) @@ -55,75 +40,9 @@ def clearNotifications(tick=0): sconn.console(javascript_clear) -def sendSMS(notification): - message = 'Screeps: ' + notification['message'] - settings = getSettings() - - if 'twilio_sid' not in settings: - print('skipping sms due to lack of settings.') - return - - smsclient = TwilioRestClient(settings['twilio_sid'], - settings['twilio_token']) - message = smsclient.messages.create( - body=message, - to=settings['sms_to'], # Replace with your phone number - from_=settings['sms_from']) # Replace with your Twilio number - print(message.sid) - - -def sendHTTP(notification): - settings = getSettings() - - if 'http' not in settings: - print('skipping http due to lack of settings.') - return - - notification['user'] = settings['screeps_username'] - headers = { - 'user-agent': 'screeps_notify', - } - - if 'api-key' in settings: - headers['x-api-key'] = settings['api-key'] - - print headers - if 'http_user' in settings: - r = requests.post(settings['http'], - json=notification, - headers=headers, - auth=(settings['http_user'], - settings['http_password'])) - else: - r = requests.post(settings['http'], - json=notification, - headers=headers) - - print r.text - print r.status_code - return r.status_code == requests.codes.ok - - class App(): - def __init__(self): - self.stdin_path = '/dev/null' - self.stdout_path = '/dev/null' - # self.stdout_path = base_directory + '/screepsnotify.out' - self.stderr_path = base_directory + '/screepsnotify.err' - self.pidfile_path = base_directory + '/screepsnotify.pid' - self.pidfile_timeout = 5 - def run(self): - logging.basicConfig(level=logging.WARN) - logger = logging.getLogger("ScreepsNotify") - logger.setLevel(logging.INFO) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s") - handler = logging.FileHandler(base_directory + "/screepsnotify.log") - handler.setFormatter(formatter) - logger.addHandler(handler) - while True: notifications = getNotifications() if not notifications or len(notifications) <= 0: @@ -134,8 +53,20 @@ def run(self): for notification in notifications: if notification['tick'] > limit: limit = notification['tick'] - sendSMS(notification) - sendHTTP(notification) + + if 'groups' in notification: + groups = notification['groups'] + else: + groups = ['default'] + + services = config.getServicesFromGroups(groups) + for service in services: + try: + driver = messenger.getMessengerDriver(service) + driver.sendMessage(notification['message']) + except: + traceback.print_exc() + clearNotifications(limit) time.sleep(5) diff --git a/screeps_notify/services/__init__.py b/screeps_notify/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/screeps_notify/services/config.py b/screeps_notify/services/config.py new file mode 100644 index 0000000..2be6f11 --- /dev/null +++ b/screeps_notify/services/config.py @@ -0,0 +1,27 @@ +import os +import sys +import yaml + +settings = False +cwd = os.getcwd() +path = cwd + '/.settings.yaml' +assert os.path.isfile(path) +with open(path, 'r') as f: + settings = yaml.load(f) + +def getSettings(): + return settings + +def getServicesFromGroups(groups): + ret_services = [] + for group in groups: + if group in settings['groups']: + if settings['groups'][group] == 'all': + return list(settings['services'].keys()) + for service in settings['groups'][group]: + if service not in ret_services: + ret_services.append(service) + if len(ret_services) < 1: + if 'default' not in groups: + return getServicesFromGroups(['default']) + return ret_services diff --git a/screeps_notify/services/messenger.py b/screeps_notify/services/messenger.py new file mode 100644 index 0000000..ccfe975 --- /dev/null +++ b/screeps_notify/services/messenger.py @@ -0,0 +1,33 @@ +import importlib +from config import settings +driver_cache = {} +service_cache = {} +assert 'services' in settings +services = settings['services'] + + +def getMessengerDriver(name): + if name in service_cache: + return service_cache[name] + if name not in services: + print('name not in services') + return False + service_settings = services[name] + if 'type' not in service_settings: + print('driver not in service settings') + return False + driver_module = getDriverModule(service_settings['type']) + driver_class = getattr(driver_module, service_settings['type']) + driver = driver_class(service_settings) + + service_cache[name] = driver + return driver + + +# Dynamically import module +def getDriverModule(driver): + if driver in driver_cache: + return driver_cache[driver] + driver = 'services.messengers.%s' % (driver,) + driver_cache[driver] = importlib.import_module(driver, 'screeps_notify') + return driver_cache[driver] diff --git a/screeps_notify/services/messengers/__init__.py b/screeps_notify/services/messengers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/screeps_notify/services/messengers/http.py b/screeps_notify/services/messengers/http.py new file mode 100644 index 0000000..42f7310 --- /dev/null +++ b/screeps_notify/services/messengers/http.py @@ -0,0 +1,34 @@ + +import services.config as config +import requests + + +class http: + + def __init__(self, settings): + self.settings = settings + + def sendMessage(self, notification): + print('sending message from http') + + notification['user'] = config.settings['screeps_username'] + headers = {'user-agent': 'screeps_notify'} + if 'api-key' in self.settings: + headers['x-api-key'] = self.settings['api-key'] + + if 'http_user' in self.settings: + r = requests.post(self.settings['url'], + json=notification, + headers=headers, + auth=(self.settings['http_user'], + self.settings['http_password'])) + else: + r = requests.post(self.settings['url'], + json=notification, + headers=headers) + + if r.status_code != requests.codes.ok: + raise ValueError( + 'http request returned an error %s, the response is:\n%s' + % (r.status_code, r.text)) + return r.status_code == requests.codes.ok diff --git a/screeps_notify/services/messengers/slack.py b/screeps_notify/services/messengers/slack.py new file mode 100644 index 0000000..4bf5e86 --- /dev/null +++ b/screeps_notify/services/messengers/slack.py @@ -0,0 +1,32 @@ + +import json +import services.config as config +import requests + + +class slack: + + def __init__(self, settings): + self.settings = settings + + def sendMessage(self, notification): + print('sending message from slack') + + url = self.settings['webhook_url'] + user = config.settings['screeps_username'] + message = '%s: %s' % (user, notification) + slack_data = {'text': message} + + r = requests.post(self.settings['webhook_url'] + data=json.dumps(slack_data), + headers={ + 'Content-Type': 'application/json', + 'user-agent': 'screeps_notify' + }) + + if r.status_code != requests.codes.ok: + raise ValueError( + 'Request to slack returned an error %s, the response is:\n%s' + % (r.status_code, r.text)) + + return r.status_code == requests.codes.ok diff --git a/screeps_notify/services/messengers/sms.py b/screeps_notify/services/messengers/sms.py new file mode 100644 index 0000000..46e0d72 --- /dev/null +++ b/screeps_notify/services/messengers/sms.py @@ -0,0 +1,27 @@ + +from twilio.rest import TwilioRestClient + + +class sms: + + def __init__(self, settings): + self.settings = settings + self.smsclient = False + + def getClient(self): + if self.smsclient: + return self.smsclient + assert 'twilio_sid' in self.settings + self.smsclient = TwilioRestClient(self.settings['twilio_sid'], + self.settings['twilio_token']) + return self.smsclient + + def sendMessage(self, notification): + print('sending message from sms') + message_text = 'Screeps: ' + notification + message = self.getClient().messages.create( + body=message_text, + to=self.settings['sms_to'], # Replace with your phone number + from_=self.settings['sms_from']) # Replace with your Twilio number + print(message.sid) + From 8e674ada4c049f80a09970a5baec7b8b54557be7 Mon Sep 17 00:00:00 2001 From: Robert Hafner Date: Sun, 25 Jun 2017 11:47:42 -0700 Subject: [PATCH 2/2] update readme with new settings format --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 75a7eac..60bad33 100644 --- a/README.md +++ b/README.md @@ -20,48 +20,95 @@ The settings file is a yaml file. Begin by copying the settings.dist file to cp .settings.dist.yaml .settings.yaml ``` -The settings file is in yaml and takes various authentication tokens. + +### Screeps Settings ```yaml -# Copy this to .settings.yaml and fill it out. # Screeps account info screeps_username: screeps_password: screeps_ptr: false +``` + + +### Define Services + +Services are used by the system to send messages. Currently there are three +service types (HTTP, Slack, and SMS), each of which can be used multiple times +(for instance, you can define multiple slack hooks to send different types of +messages to different channels). + +```yaml +services: + + sms: + # Set driver to twilio + type: sms + + # Your Account SID from www.twilio.com/console + twilio_sid: -## To enable SMS Messages fill out the information below. + # Your Auth Token from www.twilio.com/console + twilio_token: -# Your Account SID from www.twilio.com/console -twilio_sid: + # You SMS number from twilio. https://www.twilio.com/console/phone-numbers/dashboard + sms_from: '+15555555555' -# Your Auth Token from www.twilio.com/console -twilio_token: + # This should be the number you want to receive the texts. + sms_to: '+15555555555' -# You SMS number from twilio. https://www.twilio.com/console/phone-numbers/dashboard -sms_from: '+15555555555' + alliance: + # Set driver to HTTP + type: http -# This should be the number you want to receive the texts. -sms_to: '+15555555555' + # Specify a url + url: https://example.execute-api.us-east-1.amazonaws.com/prod/service + # Provide an API key for AWS Gateway (optional) + api-key: -## To enable HTTP Messages fill out the information below. + logs: + # Set driver to HTTP + type: http -# URL to post to. -http: + # Specify a url + url: https://example.execute-api.us-east-1.amazonaws.com/prod/service -# Username, if required. -http_user: + # Provide an API key for AWS Gateway (optional) + api-key: -# Password, if required. -http_pass: + slack: + # Set driver to SLACK + type: slack -# AWS Lambda API Key. -api-key: + # Specify a url + webhook_url: ``` +### Define Groups +Groups define which services get used when a notification is sent. At a minimum +a `default` group should be set. + +Groups can either be an array of services or the string `all` (which will make +sure the group sends a message to all available services). + +```yaml +groups: + + default: + - sms + - logs + - slack + + economy: + - sms + - logs + + defense: all +``` ## Installation @@ -120,6 +167,12 @@ Notify('Test Message') // Will send immediately, but only once every 100 ticks. Notify('Rate Limited Message', 100) + +// Will send only to the economy group services, and only once every 100 ticks. +Notify('Rate Limited Group Message', 100, ['economy']) + +// Will send immediately but only to the defense group services +Notify('Rate Limited Group Message', false, ['defense']) ```