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/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']) ``` 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) +