├── README.md ├── RssFetcher.py ├── WolFram_Alpha_Navoda.py ├── WolframAlpha.py ├── contact_escalation ├── smart_message_schedule_checker.py └── smart_messenger.py ├── count_recent_gitlab_commits.py ├── dropbox ├── dropbox_authenticate.py └── dropbox_monitor.py ├── example_form.py ├── firebase_connect.py ├── freebaseLookupScript.py ├── freshbooks_monitor.py ├── geckoboard_update.py ├── google-drive ├── google_drive_auth.py ├── google_drive_monitor.py └── google_update_spreadsheet.py ├── home-genie ├── README.txt ├── home-genie-script │ ├── action.cs │ ├── action.cs~ │ ├── condition.cs │ └── condition.cs~ └── scripts │ ├── home_genie_motion_sensor.py │ └── home_genie_motion_sensor.py~ ├── kato.py ├── nebri_logo.png ├── odeskJobsSearcher.py ├── odeskJobsSearcher_authentication_helper.py ├── odesk_jobs.py ├── rssWatcher.py ├── slack_connector.py ├── smartthings ├── README.md ├── forms │ ├── st_oauth_keys.py │ └── st_send_command.py ├── iotdb │ └── smarthings.groovy ├── libraries │ └── smartthings.py └── scripts │ ├── st_acces_token.py │ ├── st_authorize.py │ ├── st_example.py │ ├── st_get_devices.py │ ├── st_save_oauth_keys.py │ └── st_send_command.py ├── standup ├── forms │ ├── standup_controller_starter.py │ ├── standup_dev_form.py │ ├── standup_lookup_existing_for_editing.py │ ├── standup_reset_roster.py │ ├── standup_schedule_edit.py │ └── standup_schedule_new.py └── scripts │ ├── standup_all_dev_forms_submitted.py │ ├── standup_controller_master.py │ ├── standup_controller_per_instance.py │ ├── standup_dev_form_submitted.py │ ├── standup_dev_forms_late.py │ ├── standup_load_dev_form.py │ ├── standup_load_edit_form.py │ ├── standup_new_standup_schedule.py │ ├── standup_reset_roster.py │ └── standup_schedule_edit.py ├── stripe_connector ├── thermostat.py ├── trello ├── trello_log_nonauthor_changes.py └── trello_nonauthor_change_alert.py ├── twilio_sms.py ├── twitter_connect.py ├── url_monitoring ├── add-url-monitor.html ├── add_url_monitor.py ├── monitored-urls.html ├── remove_url_monitor.py ├── show_monitored_urls.py ├── url-entry.html └── url_contact_map.py ├── wolfram_alpha.py └── yahoo_finance_connector.py /README.md: -------------------------------------------------------------------------------- 1 | ![Nebri - Event Driven Python Development Platform](https://raw.githubusercontent.com/adamhub/nebri/master/nebri_logo.png) 2 | ================ 3 | **Nebri** is a Event Driven Python Development Platform. You can Create complex (Python) rules to interact with People and API's. **This repo is full of example scripts that are built for Nebri**. You can take these scripts and adapt them for your own needs. Visit [Nebrios.com](https://nebrios.com). 4 | 5 | Nebri is similar to IFTTT except it's completely controlled by Python. You have nearly unlimited logical controls over your rules. Also any Pypi package can be installed and used in your rule scripts. 6 | 7 | Platform Features 8 | ----------------- 9 | * Rule evaluation and action 10 | * Connect to any API 11 | * Integrate any Pypi package 12 | * Can replace BPM and CEP and Workflow software 13 | * Can control/respond to sensors 14 | * Powerful for home automation (smart home) 15 | * Internet of Things (IoT) rules platform 16 | * Child processes 17 | * Access Control 18 | * Crons 19 | -------------------------------------------------------------------------------- /RssFetcher.py: -------------------------------------------------------------------------------- 1 | from feedparser import feedparser 2 | 3 | class rssfeed(NebriOS): 4 | listens_to = ['shared.watch_list'] 5 | 6 | def check(self): 7 | return True 8 | 9 | def action(self): 10 | feed = feedparser.parse("http://s1.dlnws.com/dealnews/rss/editors-choice.xml") 11 | list_of_products_brands = shared.watch_list.splitlines() 12 | matches_from_feed = set() 13 | for i in range(0, len(feed['entries'])): 14 | for keyword in list_of_products_brands: 15 | if keyword in feed['entries'][i].title: 16 | matches_from_feed.add(feed['entries'][i].title) 17 | shared.deal_list = '\n'.join(matches_from_feed) 18 | -------------------------------------------------------------------------------- /WolFram_Alpha_Navoda.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import wolframalpha 3 | import urllib2 4 | from xml.etree import ElementTree as etree 5 | 6 | 7 | class wolfram(object): 8 | 9 | def __init__(self, appid): 10 | self.appid = appid 11 | 12 | def search(self, resp): 13 | client = wolframalpha.Client(self.appid) 14 | res = client.query(resp) 15 | if len(res.pods) > 0: 16 | texts = "" 17 | pod = res.pods[1] 18 | if pod.text: 19 | texts = pod.text 20 | Res_Split = texts.split('.') 21 | arg_1 = Res_Split[0] 22 | revenue = arg_1[1:] 23 | try: 24 | if int(revenue) >= 300: 25 | self.send_email("test@examples.com", '''Hi, Google has reached their revenue to $300b''') 26 | else: 27 | print "Google has not reached it's revenue to $300b yet." 28 | except: 29 | print "Erorr in output." 30 | else: 31 | texts = "There is no result." 32 | else: 33 | print "Sorry, no Pods found." 34 | 35 | 36 | if __name__ == "__main__": 37 | schedule = "0 0 * * *" 38 | appid = 'T8WRWQ-7X2GVQ6Y3V' 39 | query = 'Google Revenue' 40 | w = wolfram(appid) 41 | w.search(query) 42 | -------------------------------------------------------------------------------- /WolframAlpha.py: -------------------------------------------------------------------------------- 1 | ''' This script will send a mail to the client when Google reaches their revenue to $300b. ''' 2 | 3 | import urllib2 4 | import urllib 5 | import httplib 6 | from xml.etree import ElementTree as etree 7 | import smtplib 8 | 9 | 10 | class wolfram(object): 11 | 12 | def __init__(self, appid): 13 | self.appid = appid 14 | self.base_url = 'http://api.wolframalpha.com/v2/query?' 15 | self.headers = {'User-Agent': None} 16 | 17 | 18 | def _get_xml(self, ip): 19 | url_params = {'input': ip, 'appid': self.appid} 20 | data = urllib.urlencode(url_params) 21 | req = urllib2.Request(self.base_url, data, self.headers) 22 | xml = urllib2.urlopen(req).read() 23 | return xml 24 | 25 | 26 | def _xmlparser(self, xml): 27 | data_dics = {} 28 | tree = etree.fromstring(xml) 29 | # retrieving every tag with label 'plaintext' 30 | for e in tree.findall('pod'): 31 | for item in [ef for ef in list(e) if ef.tag == 'subpod']: 32 | for it in [i for i in list(item) if i.tag == 'plaintext']: 33 | if it.tag == 'plaintext': 34 | data_dics[e.get('title')] = it.text 35 | return data_dics 36 | 37 | 38 | def search(self, ip): 39 | # This function will query about Google's revenue using WolframAlpha API 40 | xml = self._get_xml(ip) 41 | result_dics = self._xmlparser(xml) 42 | titles = result_dics.keys() 43 | Res = result_dics['Result'] 44 | Res_Split = Res.split('.') 45 | arg_1 = Res_Split[0] 46 | revenue = arg_1[1:] 47 | try: 48 | if int(revenue) >= 300: 49 | self.send_mails() 50 | else: 51 | print "Google has not reached it's revenue to $300b yet." 52 | except: 53 | print "Erorr in output." 54 | 55 | 56 | def send_mails(self): 57 | gmail_user = "test@test.com" 58 | gmail_pwd = "test@123" 59 | FROM = 'test@test.com' 60 | TO = ['test1@test.com'] #must be a list 61 | SUBJECT = "Google has reached it's revenue to $300b" 62 | TEXT = "Hi,\n\nThis is to inform you that Google has reached it's revenue to $300b.\n \nRegards,\nAutomatic Mail Sender." 63 | 64 | # Prepare actual message 65 | message = """\From: %s\nTo: %s\nSubject: %s\n\n%s 66 | """ % (FROM, ", ".join(TO), SUBJECT, TEXT) 67 | try: 68 | #server = smtplib.SMTP(SERVER) 69 | server = smtplib.SMTP("smtp.test.com", 25) 70 | server.ehlo() 71 | server.starttls() 72 | server.login(user_name, password) 73 | server.sendmail(FROM, TO, message) 74 | #server.quit() 75 | server.close() 76 | print 'Google has reached their revenue to $300b.\nSuccessfully sent the mail' 77 | except: 78 | print "Failed to send mail" 79 | 80 | 81 | if __name__ == "__main__": 82 | schedule = "0 0 * * *" 83 | appid = 'T8WRWQ-7X2GVQ6Y3V' 84 | query = 'Google Revenue' 85 | w = wolfram(appid) 86 | w.search(query) 87 | 88 | -------------------------------------------------------------------------------- /contact_escalation/smart_message_schedule_checker.py: -------------------------------------------------------------------------------- 1 | import twilio, copy 2 | from twilio.rest import TwilioRestClient 3 | 4 | class smart_message_schedule_checker(NebriOS): 5 | schedule = "0 * * * *" 6 | 7 | # twilio creds (test only) 8 | # TODO: change this to bixly creds 9 | TWILIO_ACCOUNT_SID = "" 10 | TWILIO_AUTH_TOKEN = "" 11 | TWILIO_NUMBER = "+15555555555" 12 | 13 | def check(self): 14 | return shared.sms_schedule and len(shared.sms_schedule) 15 | 16 | def send_sms(self, to, msg): 17 | # initialize twilio client and send SMS 18 | try: 19 | client = TwilioRestClient(self.TWILIO_ACCOUNT_SID, self.TWILIO_AUTH_TOKEN) 20 | status = client.messages.create(body=msg, from_=self.TWILIO_NUMBER, to=to) 21 | return True 22 | except twilio.TwilioRestException as e: 23 | raise Exception("Could not send SMS. Error: %s" % e) 24 | 25 | def action(self): 26 | self.status = "Ran" 27 | self.ran_at = datetime.now() 28 | 29 | self.current_datetime = self.ran_at 30 | if shared.system_datetime: 31 | dt = shared.system_datetime 32 | self.current_datetime = datetime( 33 | dt.year, dt.month, dt.day, 34 | dt.hour, dt.minute, dt.second 35 | ) 36 | 37 | sms_schedules = copy.copy(shared.sms_schedule) 38 | for record in sms_schedules: 39 | if (self.current_datetime < record['schedule'] and 40 | record['schedule'] <= self.current_datetime + timedelta(hours=1)): 41 | 42 | # Record only as a KVP if datetime is 43 | # spoofed and do not send message 44 | if shared.system_datetime: 45 | self.cef_test_result_message = { 46 | 'sender': record['sender'], 47 | 'message': record['message'], 48 | 'schedule': record['schedule'], 49 | 'recipient': record['recipient'] 50 | } 51 | else: 52 | self.send_sms(record['recipient_phone'], record['message']) 53 | shared.sms_schedule.remove(record) 54 | 55 | -------------------------------------------------------------------------------- /contact_escalation/smart_messenger.py: -------------------------------------------------------------------------------- 1 | import twilio 2 | from twilio.rest import TwilioRestClient 3 | from contact_escalation_framework.cef_framework import get_contact, get_next_available, contact_available 4 | 5 | class smart_messenger(NebriOS): 6 | listens_to = ['smart_message'] 7 | 8 | # twilio creds (test only) 9 | # TODO: change this to bixly creds 10 | TWILIO_ACCOUNT_SID = "" 11 | TWILIO_AUTH_TOKEN = "" 12 | TWILIO_NUMBER = "+15555555555" 13 | 14 | def check(self): 15 | # check if all 3 params are present 16 | if (self.smart_message['to'] and 17 | self.smart_message['message'] and 18 | self.smart_message['priority']): 19 | 20 | return True 21 | return False 22 | 23 | def send_sms(self, to, msg): 24 | # initialize twilio client and send SMS 25 | try: 26 | client = TwilioRestClient(self.TWILIO_ACCOUNT_SID, self.TWILIO_AUTH_TOKEN) 27 | status = client.messages.create(body=msg, from_=self.TWILIO_NUMBER, to=to) 28 | self.progress = 'SMS sent' 29 | return True 30 | except twilio.TwilioRestException as e: 31 | self.progress = 'SMS sending failed' 32 | raise Exception("Could not send SMS. Error: %s" % e) 33 | 34 | def action(self): 35 | self.ran_at = datetime.now() 36 | 37 | recipient = self.smart_message['to'] 38 | message = self.smart_message['message'] 39 | priority = self.smart_message['priority'] 40 | 41 | # get specific user's data 42 | self.progress = "Retrieving user info..." 43 | profile = get_contact(recipient, shared.contacts) 44 | 45 | current_datetime = datetime.now() 46 | if shared.system_datetime: 47 | dt = shared.system_datetime 48 | current_datetime = datetime( 49 | dt.year, dt.month, dt.day, 50 | dt.hour, dt.minute, dt.second 51 | ) 52 | 53 | if not profile: 54 | # if profile returned None, user has no record in contacts, do nothing 55 | self.progress = "User info does not exist. Aborting..." 56 | return False 57 | 58 | if priority == 1: 59 | # send SMS now 60 | self.progress = "Sending email and SMS..." 61 | self.send_sms(profile['phone'], message) 62 | 63 | # always send email regardless of priority 64 | send_email(profile['email'], message) 65 | elif priority == 2: 66 | is_available = contact_available(recipient, profile, current_datetime) 67 | 68 | if is_available: 69 | # send SMS now 70 | self.progress = "Sending SMS..." 71 | self.send_sms(profile['phone'], message) 72 | else: 73 | # schedule SMS 74 | self.progress = "Scheduling SMS..." 75 | next_available = get_next_available(profile, current_datetime) 76 | 77 | if not shared.sms_schedule: 78 | shared.sms_schedule = [] 79 | shared.sms_schedule.append({ 80 | 'message': message, 81 | 'sender': self.last_actor, 82 | 'schedule': next_available, 83 | 'recipient': profile['user'], 84 | 'recipient_phone': profile['phone'] 85 | }) 86 | 87 | # always send email regardless of priority 88 | self.progress = "Sending email..." 89 | send_email(profile['email'], message) 90 | elif priority == 3: 91 | # always send email regardless of priority 92 | self.progress = "Sending email..." 93 | #send_email(profile['email'], message) 94 | send_email(profile['email'], message) 95 | else: 96 | # invalid priority number 97 | self.progress = "Invalid data. Aborting..." 98 | raise Exception("Invalid priority number provided.") 99 | -------------------------------------------------------------------------------- /count_recent_gitlab_commits.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | # NOTE: requires pyapi-gitlab 4 | # $> pip install pyapi-gitlab python-dateutil pytz 5 | 6 | import gitlab 7 | import pytz 8 | 9 | from dateutil import parser 10 | 11 | 12 | class count_recent_gitlab_commits(NebriOS): 13 | listens_to = ['count_recent_gitlab_commits'] 14 | 15 | GITLAB_PROJECT_ID = 237021 16 | 17 | 18 | def check(self): 19 | return self.count_recent_gitlab_commits 20 | 21 | def get_project_info_cache_key(self): 22 | return shared.GITLAB_PROJECT_INFO_CACHE_KEY_FORMAT % self.GITLAB_PROJECT_ID 23 | 24 | def action(self): 25 | shared.GITLAB_PROJECT_INFO_CACHE_KEY_FORMAT = 'GITLAB_PROJECT_INFO_%s' 26 | shared.GITLAB_HOST = 'http://gitlab.com' 27 | shared.GITLAB_PRIVATE_TOKEN = 'r_zxYTQg51R55baCa__j' 28 | 29 | gl = gitlab.Gitlab(shared.GITLAB_HOST, token=shared.GITLAB_PRIVATE_TOKEN) 30 | 31 | # Get project info first 32 | project_info_cache_key = self.get_project_info_cache_key() 33 | project_info_cache = getattr(shared, project_info_cache_key, None) 34 | if not project_info_cache: 35 | project_info_cache = gl.getproject(self.GITLAB_PROJECT_ID) 36 | project_info_cache['recent_commits_count'] = 0 37 | 38 | # Compute now in UTC 39 | utc_now = datetime.datetime.now(tz=pytz.utc) 40 | utc_past_day = utc_now - datetime.timedelta(hours=24) 41 | qualified_commits = list() 42 | 43 | for commit in gl.getall(gl.getrepositorycommits, self.GITLAB_PROJECT_ID, page=0): 44 | created_at = parser.parse(commit['created_at']) 45 | utc_created_at = created_at.astimezone(pytz.utc) 46 | 47 | if utc_created_at >= utc_past_day: 48 | qualified_commits.append(commit) 49 | 50 | # Save the data 51 | project_info_cache['recent_commits_count'] = len(qualified_commits) 52 | setattr(shared, project_info_cache_key, project_info_cache) 53 | -------------------------------------------------------------------------------- /dropbox/dropbox_authenticate.py: -------------------------------------------------------------------------------- 1 | import dropbox 2 | 3 | class dropbox_authenticate(NebriOS): 4 | listens_to = ['shared.dropbox_secret', 'shared.dropbox_key', 'shared.code', 'db_test'] 5 | 6 | def check(self): 7 | return shared.dropbox_secret and shared.dropbox_key 8 | 9 | def action(self): 10 | flow = dropbox.client.DropboxOAuth2FlowNoRedirect(shared.dropbox_key, shared.dropbox_secret) 11 | if shared.code: 12 | try: 13 | shared.dropbox_token, shared.dropbox_user_id = flow.finish(shared.code) 14 | except dropbox.rest.ErrorResponse as e: 15 | send_email(self.last_actor, 16 | "Hello, Your code did not work; please click the link and enter the new confirmation code in the form {{authorize_url}} shared_code := - Please enter the confirmation code\n-Nebri OS", 17 | subject="Nebri OS Dropbox" 18 | ) 19 | else: 20 | self.authorize_url = flow.start() 21 | send_email(self.last_actor, 22 | "Hello, Please click the link and enter the confirmation code in the form {{authorize_url}} shared_code := - Please enter the confirmation code\n-Nebri OS", 23 | subject="Nebri OS Dropbox" 24 | ) 25 | -------------------------------------------------------------------------------- /dropbox/dropbox_monitor.py: -------------------------------------------------------------------------------- 1 | import dropbox 2 | 3 | class dropbox_monitor(NebriOS): 4 | listens_to = ["dropbox_monitor_go", ] 5 | 6 | def check(self): 7 | flow = dropbox.client.DropboxOAuth2FlowNoRedirect(shared.dropbox_key, shared.dropbox_secret) 8 | client = dropbox.client.DropboxClient(shared.dropbox_token) 9 | 10 | search = client.search("/", "report.xlx", file_limit=1000, include_deleted=False) 11 | if search: 12 | for item in search: 13 | current_time = datetime.now() 14 | modified_time = item['modified'] 15 | modified_time = datetime.strptime(modified_time, "%a, %d %b %Y %H:%M:%S +0000") 16 | delta = current_time - modified_time 17 | days = int(delta.days) 18 | if days <= 7: 19 | return True 20 | 21 | def action(self): 22 | send_email("me@example.com","The report is late!!!") 23 | -------------------------------------------------------------------------------- /example_form.py: -------------------------------------------------------------------------------- 1 | class hello_form(Form): 2 | 3 | def validate_bool_test_one(form, field, value): 4 | if value == False: 5 | raise ValidationError('Bool test one cannot be False!') 6 | 7 | def validate_age(form, field, age): 8 | if age < 18: 9 | field.value = 10 10 | raise ValidationError('You must be older than 18 to submit this form!') 11 | elif age > 99: 12 | raise ValidationError('Who are you? Superhuman?!') 13 | 14 | def validate_requested_on(form, field, value): 15 | field.value = datetime.now() 16 | 17 | def validate_requested_date(form, field, value): 18 | if not value > date.today(): 19 | raise ValidationError('Date must be in the future!') 20 | 21 | form_title = 'Hello World!' 22 | form_instructions = 'Click on fields and try to fill them out!' 23 | 24 | name = String(label="Provide your name", message='Test message') 25 | age = Number(label="Provide your age", message='Test', required=True, validation=validate_age) 26 | other_number = Number(initial=2) 27 | bool_test_one = Boolean(required=True, initial=False, validation=validate_bool_test_one) 28 | bool_test_two = Boolean(required=True, initial=False, radio=True) 29 | bool_test_three = Boolean(required=True, initial=False, dropdown=True) 30 | combo_box_one = String(choices=[('AK', 'Arkansas',),('CA', 'California',)]) 31 | combo_box_two = Number(choices=[(1, 'One',),(2, 'Two',)]) 32 | multi_box_one = String(choices=[(1, 'One',),(2, 'Two',)], multiselect=True) 33 | when = DateTime(initial=datetime.now()) 34 | requested_on = DateTime(initial=datetime.now(), validation=validate_requested_on) 35 | requested_date = Date(initial=date.today(), validation=validate_requested_date) 36 | requested_time = Time(initial=datetime.now()) 37 | -------------------------------------------------------------------------------- /firebase_connect.py: -------------------------------------------------------------------------------- 1 | from firebase import firebase # from python-firebase 2 | 3 | 4 | class firebase_connect(NebriOS): 5 | listens_to = ['trigger_firebase_write'] 6 | 7 | def check(self): 8 | return self.trigger_firebase_write == True 9 | 10 | def action(self): 11 | self.trigger_firebase_write = "RAN" 12 | firebase_url = shared.FIREBASE_URL # sample: "https://example.firebaseio.com" 13 | value = "test_value" 14 | location = "" # can be any location under the firebase url (e.g. '/users') or blank for root 15 | 16 | secret = shared.FIREBASE_SECRET 17 | email = shared.FIREBASE_EMAIL # not really needed. can be blank 18 | authentication = None 19 | if secret: 20 | authentication = firebase.FirebaseAuthentication(secret, email) 21 | 22 | fbase = firebase.FirebaseApplication(firebase_url, authentication) 23 | 24 | result = fbase.post(location, value, params={'print': 'pretty'}, headers={'X_FANCY_HEADER': 'VERY FANCY'}, connection=None) 25 | 26 | self.fetched = result 27 | -------------------------------------------------------------------------------- /freebaseLookupScript.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib 3 | from datetime import date 4 | 5 | 6 | def get_new_books(favorite_authors): 7 | """ 8 | (list) -> (list of lists) 9 | 10 | take a list of authors to be checked 11 | return a list of lists of type 12 | [[authors' names], date of publication, title of the book] 13 | containing info about books that are published 14 | by favorite_authors during the current month 15 | """ 16 | service_url = 'https://www.googleapis.com/freebase/v1/mqlread' 17 | 18 | 19 | api_key = 'some_key' #change api_key manually 20 | 21 | current_month = str(date(date.today().year, date.today().month, 1)) 22 | next_month = str(date(date.today().year, date.today().month + 1, 1)) 23 | response = [] 24 | 25 | for mid in favorite_authors: 26 | query = [{ 27 | "type": "/book/book", 28 | "name": None, 29 | "/book/written_work/author": [{"mid": mid}], 30 | "/book/written_work/date_of_first_publication>": 31 | "%s" % (current_month), 32 | "ns0:/book/written_work/date_of_first_publication<": 33 | "%s" % (next_month), 34 | "/book/written_work/date_of_first_publication": None, 35 | "ns0:/book/written_work/author": [] 36 | }] 37 | 38 | params = { 39 | 'query': json.dumps(query), 40 | 'key': api_key 41 | } 42 | url = service_url + '?' + urllib.urlencode(params) 43 | response.append(json.loads(urllib.urlopen(url).read())) 44 | 45 | values = [] 46 | 47 | for res in response: 48 | for i in res['result']: 49 | values.append([[str(a) for a in i['ns0:/book/written_work/author']], 50 | str(i["/book/written_work/date_of_first_publication"]), 51 | str(i['name'])]) 52 | return values 53 | 54 | 55 | class my_class(NebriOS): 56 | schedule = "0 0 * * *" 57 | 58 | def check(self): 59 | global new_releases 60 | favorite_authors = ['/m/0675ss', '/m/0jt90f5'] 61 | all_new_releases = get_new_books(favorite_authors) 62 | new_releases = [book for book in all_new_releases \ 63 | if book not in shared.favorite_author_releases] 64 | return bool(new_releases) 65 | 66 | def action(self): 67 | shared.favorite_author_releases += new_releases 68 | -------------------------------------------------------------------------------- /freshbooks_monitor.py: -------------------------------------------------------------------------------- 1 | from refreshbooks import api 2 | 3 | class freshbooks_monitor(NebriOS): 4 | listens_to = ['check_freshbooks'] 5 | 6 | def check(self): 7 | client = api.TokenClient(shared.fresh_user, shared.fresh_key, user_agent='Nebri/1.0') 8 | 9 | now = datetime.now() 10 | 11 | invoice_response = client.invoice.list() 12 | 13 | invoice_names = [] 14 | 15 | for invoice in invoice_response.invoices.invoice: 16 | if (invoice.status != 'paid' and float(str(invoice.amount)) > 3000 and (now - datetime.strptime(invoice.date, '%Y-%m-%d %H:%M:%S')).days >= 3): 17 | invoice_names.append(invoice.invoice_id) 18 | 19 | if invoice_names: 20 | self.invoice_names = invoice_names 21 | return True 22 | 23 | return False 24 | 25 | def action(self): 26 | send_email('me@example.com', """ 27 | Invoice(s) %s are over 3 weeks old and have not been paid. 28 | """ % (', '.join(self.invoice_names))) 29 | self.invoices = True 30 | -------------------------------------------------------------------------------- /geckoboard_update.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | class geckoboard_update(NebriOS): 5 | listens_to = ['shared.geckoboard_data'] 6 | 7 | def check(self): 8 | if shared.geckoboard_data: 9 | return True 10 | return False 11 | 12 | def action(self): 13 | self.status = "Ran" 14 | self.ran_at = datetime.now() 15 | 16 | # geckoboard api key 17 | api_key = "" 18 | 19 | payload = {} 20 | payload['api_key'] = api_key 21 | payload['data'] = shared.geckoboard_data['data'] 22 | 23 | r = requests.post(shared.geckoboard_data['push_url'], data=json.dumps(payload)) 24 | if r.status_code == 200: 25 | self.status = "Success" 26 | else: 27 | raise Exception("Could not post geckoboard update."\ 28 | " Error Code: {}".format(r.status_code)) 29 | -------------------------------------------------------------------------------- /google-drive/google_drive_auth.py: -------------------------------------------------------------------------------- 1 | import httplib2 2 | 3 | from apiclient.discovery import build 4 | from oauth2client.client import OAuth2WebServerFlow 5 | 6 | # scope now includes google docs spreadsheet as well as the drive 7 | OAUTH_SCOPE = ['https://www.googleapis.com/auth/drive', 8 | 'https://spreadsheets.google.com/feeds', 9 | 'https://docs.google.com/feeds'] 10 | REDIRECT_URI = 'https://www.example.com/oauth2callback' 11 | 12 | 13 | class google_auth(NebriOS): 14 | listens_to = ['shared.client_key', 'shared.client_secret', 15 | 'shared.client_code', 'googleauth_test'] 16 | 17 | def check(self): 18 | return shared.client_key and shared.client_secret 19 | 20 | def action(self): 21 | flow = OAuth2WebServerFlow(shared.client_key, shared.client_secret, 22 | OAUTH_SCOPE, REDIRECT_URI) 23 | authorize_url = flow.step1_get_authorize_url() 24 | 25 | if shared.client_code: 26 | shared.client_credentials = flow.step2_exchange(shared.client_code).to_json() 27 | else: 28 | self.authorize_url = flow.step1_get_authorize_url() 29 | send_email("keving@bixly.com", 30 | "Hello, Please click the link and enter the confirmation code in the form {{authorize_url}} shared_drive_code := - Please enter the confirmation code\n-Nebri OS", 31 | subject="Nebri OS Google Auth" 32 | ) 33 | -------------------------------------------------------------------------------- /google-drive/google_drive_monitor.py: -------------------------------------------------------------------------------- 1 | import httplib2 2 | from datetime import datetime 3 | 4 | from oauth2client.client import OAuth2Credentials 5 | from apiclient.discovery import build 6 | from apiclient import errors 7 | 8 | def get_all_files(service): 9 | result = [] 10 | page_token = None 11 | 12 | while True: 13 | try: 14 | param = {} 15 | if page_token: 16 | param['pageToken'] = page_token 17 | files = service.files().list(**param).execute() 18 | 19 | result.extend(files['items']) 20 | page_token = files.get('nextPageToken') 21 | if not page_token: 22 | break 23 | except errors.HttpError, error: 24 | break 25 | 26 | return result 27 | 28 | class google_drive_monitor(NebriOS): 29 | listens_to = ['monitor_drive'] 30 | 31 | def check(self): 32 | credentials = OAuth2Credentials.from_json(shared.client_credentials) 33 | http = httplib2.Http() 34 | http = credentials.authorize(http) 35 | 36 | drive_service = build('drive', 'v2', http=http) 37 | 38 | files = get_all_files(drive_service) 39 | 40 | for file in files: 41 | if file['title'] == 'report.otd': 42 | current_time = datetime.now() 43 | self.modified_time = file['modifiedDate'] 44 | delta = current_time - self.modified_time.replace(tzinfo=None) 45 | days = int(delta.days) 46 | if days <= 7: 47 | return True 48 | 49 | return False 50 | 51 | def action(self): 52 | self.new_file_drive = True 53 | -------------------------------------------------------------------------------- /google-drive/google_update_spreadsheet.py: -------------------------------------------------------------------------------- 1 | import gspread 2 | from oauth2client.client import OAuth2Credentials 3 | 4 | 5 | class google_update_spreadsheet(NebriOS): 6 | listens_to = ['shared.update_gsheet'] 7 | 8 | def check(self): 9 | if shared.client_credentials and \ 10 | shared.update_gsheet['filename'] and \ 11 | shared.update_gsheet['data']: 12 | return True 13 | return True 14 | 15 | def action(self): 16 | credentials = OAuth2Credentials.from_json(shared.client_credentials) 17 | gc = gspread.authorize(credentials) 18 | 19 | # get worksheet 20 | sh = gc.open(shared.update_gsheet['filename']) 21 | worksheet = sh.get_worksheet(0) 22 | 23 | # data 24 | datalist = shared.update_gsheet['data'] 25 | 26 | # loop through each data for row 27 | row = 1 28 | for k in range(0, len(datalist)): 29 | col_num = len(datalist[k]) 30 | col_letter = chr(ord('A') + col_num - 1) 31 | 32 | row_range = 'A{row}:{col}{row}'.format(row=row, col=col_letter) 33 | cell_list = worksheet.range(row_range) 34 | 35 | # loop through data and write in cell 36 | for i in range(0, len(cell_list)): 37 | cell_list[i].value = datalist[k][i] 38 | worksheet.update_cells(cell_list) 39 | row += 1 40 | -------------------------------------------------------------------------------- /home-genie/README.txt: -------------------------------------------------------------------------------- 1 | Home Genie Home Automation 2 | ========================== 3 | 4 | First, to use this script you will have to set up an email account for Home Genie to use. To do this do the following: 5 | Home > Configure > Automation > Messaging and Social > E-mail Account > Enter relevant information here 6 | 7 | Add the scripts to Home Genie by doing the following: 8 | Home > Configure > Automation > Add Group (name it Nebri or whatever you want) > Enter Group > Actions > Add new program (name it motion sensor) > Program Type to C# > copy code from home-genie-script/action.cs into Program Code > Compile > copy code from home-genie-script/condition.cs into Trigger Code > Compile 9 | 10 | Be sure to change email and sensor names and the like to match your setup. 11 | 12 | Add the simple NebriOS script to your NebriOS instance and trigger the motion sensor. 13 | -------------------------------------------------------------------------------- /home-genie/home-genie-script/action.cs: -------------------------------------------------------------------------------- 1 | var mySensorValue = Modules.WithName("motion_sensor").Get().Parameter("Sensor.Generic").DecimalValue.ToString(); 2 | 3 | Net.SendMessage("risethink@nebrios.com", "", "hg_motion := " + mySensorValue); 4 | -------------------------------------------------------------------------------- /home-genie/home-genie-script/action.cs~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamhub/nebri/1b28571e0fe3553c60f4cabbb6cf1c4ca096ce3e/home-genie/home-genie-script/action.cs~ -------------------------------------------------------------------------------- /home-genie/home-genie-script/condition.cs: -------------------------------------------------------------------------------- 1 | // Remember to Change 'SensorName' to the correct name of your sensor 2 | var mySensor = Modules.WithName("motion_sensor").Get(); 3 | 4 | // Debug code 5 | // Program.Notify("Debug", mySensor.Parameter("Sensor.Generic").DecimalValue.ToString()); 6 | 7 | // Returns 'True' if the value of the sensor is 38 or higher 8 | return (mySensor.WasFound && mySensor.Parameter("Sensor.Generic").DecimalValue >= 1); 9 | -------------------------------------------------------------------------------- /home-genie/home-genie-script/condition.cs~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamhub/nebri/1b28571e0fe3553c60f4cabbb6cf1c4ca096ce3e/home-genie/home-genie-script/condition.cs~ -------------------------------------------------------------------------------- /home-genie/scripts/home_genie_motion_sensor.py: -------------------------------------------------------------------------------- 1 | class home_genie_motion_sensor(NebriOS): 2 | listens_to = ['hg_motion'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | self.ran = True 9 | -------------------------------------------------------------------------------- /home-genie/scripts/home_genie_motion_sensor.py~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamhub/nebri/1b28571e0fe3553c60f4cabbb6cf1c4ca096ce3e/home-genie/scripts/home_genie_motion_sensor.py~ -------------------------------------------------------------------------------- /kato.py: -------------------------------------------------------------------------------- 1 | import json 2 | import httplib2 3 | 4 | 5 | class send_to_kato(NebriOS): 6 | KATO_HTTP_POST_FORMAT = 'https://api.kato.im/rooms/%s/simple' 7 | 8 | # Fill in your room id 9 | KATO_ROOM_ID = '' 10 | KATO_HTTP_POST_ENDPOINT = KATO_HTTP_POST_FORMAT % (KATO_ROOM_ID,) 11 | 12 | listens_to = ['send_to_kato'] 13 | 14 | 15 | def check(self): 16 | return u'%s' % self.send_to_kato 17 | 18 | def action(self): 19 | data = { 20 | 'renderer': 'markdown', 21 | 'text': u'%s' % self.send_to_kato 22 | } 23 | 24 | headers = { 25 | 'content-type': 'application/json' 26 | } 27 | 28 | h = httplib2.Http('.cache') 29 | (resp, content) = h.request( 30 | self.KATO_HTTP_POST_ENDPOINT, 31 | 'POST', 32 | body=json.dumps(data), 33 | headers=headers 34 | ) 35 | -------------------------------------------------------------------------------- /nebri_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamhub/nebri/1b28571e0fe3553c60f4cabbb6cf1c4ca096ce3e/nebri_logo.png -------------------------------------------------------------------------------- /odeskJobsSearcher.py: -------------------------------------------------------------------------------- 1 | import odesk 2 | import json 3 | import datetime 4 | import dateutil.parser 5 | import json 6 | 7 | 8 | #pip install python-dateutil 9 | #pip install python-odesk 10 | 11 | 12 | #API keys of your application (this script) 13 | #Get it from https://www.odesk.com/services/api/keys 14 | public_key = "todo" 15 | secret_key = "todo" 16 | 17 | #Tokens for user that authorized this app 18 | #Use odeskJobsSearcher_authentication_helper.py for getting this tokens 19 | oauth_access_token = "todo" 20 | oauth_access_token_secret = "todo" 21 | 22 | #Job search parameters, tune it as needed 23 | #(reference: https://developers.odesk.com/?lang=python#jobs_search-for-jobs) 24 | max_date_created_offset = 7 # maximum age of job (in days) 25 | job_search_parameter = {'job_status': 'open', 26 | 'days_posted': 5, 27 | 'budget': '1000', 28 | 'client_hires':'15', 29 | 'skills': 'python'} 30 | 31 | 32 | 33 | 34 | class odeskJobsSearcher(NebriOS): 35 | 36 | listens_to = ['get_odesk_jobs'] 37 | schedule = "0 0 * * *" # daily 38 | 39 | 40 | def check_date_is_within_offset(self, date, offset): 41 | """Checks if given date is withing given offset of 42 | days from today (in past)""" 43 | smallest_allowed_date = datetime.date.today() - datetime.timedelta( 44 | days=offset) 45 | job_date = dateutil.parser.parse(date).date() 46 | return job_date >= smallest_allowed_date 47 | 48 | def convert_job(self, job): 49 | """Filters unneeded fields from given job, and 50 | returns job with only title, url and date_created fields""" 51 | try: 52 | converted_job = {'title':job['title'], 53 | 'url':job['url'], 54 | 'date_created':job['date_created']} 55 | except Exception, e: 56 | print "Job conversion error. Looks like API problem" 57 | raise e 58 | 59 | return converted_job 60 | 61 | def get_jobs_list(self, client, jobs_parameter): 62 | """Searches for all available jobs on oDesk accordingly 63 | to given jobs_parameter search query, then filters 64 | out too old jobs and returns title, url and 65 | date_created fields for each job""" 66 | jobs_api_response = [] 67 | current_page_offset = 0 68 | max_page_size = 100 69 | 70 | while True: 71 | try: 72 | current_jobs_api_response = client.provider_v2.search_jobs( 73 | jobs_parameter, 74 | page_offset = current_page_offset, 75 | page_size = max_page_size) 76 | 77 | except Exception, e: 78 | print "API or http error occured, can't do much there :-(" 79 | raise e 80 | 81 | jobs_api_response += current_jobs_api_response 82 | 83 | #check for last page 84 | if len(current_jobs_api_response) < max_page_size: 85 | break 86 | 87 | #update offset to match next page 88 | current_page_offset += max_page_size 89 | 90 | try: 91 | #filter jobs with date_created > max_date_created 92 | #get only needed data from jobs 93 | jobs = [self.convert_job(i) for i in jobs_api_response if 94 | self.check_date_is_within_offset(i['date_created'], 95 | max_date_created_offset)] 96 | except Exception, e: 97 | print "API or https error occured, or there is API change" 98 | raise e 99 | 100 | return jobs 101 | 102 | def jobs_search(self): 103 | client = odesk.Client(public_key, secret_key, 104 | oauth_access_token=oauth_access_token, 105 | oauth_access_token_secret=oauth_access_token_secret) 106 | 107 | jobs = self.get_jobs_list(client=client, 108 | jobs_parameter=job_search_parameter) 109 | 110 | return jobs 111 | 112 | 113 | def check(self): 114 | jobs = self.jobs_search() 115 | #check passes only if there is at least one job found 116 | if len(jobs) > 0: 117 | self.jobs = json.dumps(jobs) 118 | return True 119 | return False 120 | 121 | def action(self): 122 | #update shared variable 123 | shared.compatible_odesk_jobs = self.jobs 124 | return 125 | 126 | -------------------------------------------------------------------------------- /odeskJobsSearcher_authentication_helper.py: -------------------------------------------------------------------------------- 1 | import odesk 2 | 3 | def api_authorization(): 4 | 5 | print """ 6 | Use 7 | https://www.odesk.com/services/api/keys 8 | for getting API keys for your app. 9 | It can take up to 24 hours to get your keys. 10 | Use "desktop" application type for your API keys request. 11 | """ 12 | 13 | public_key = raw_input('Please enter public key ("Key" field): > ') 14 | secret_key = raw_input('Please enter secret key ("Secret" field): > ') 15 | 16 | try: 17 | client = odesk.Client(public_key, secret_key) 18 | verifier = raw_input( 19 | '\nPlease enter the verification code you get ' 20 | 'following this link:\n{0}\n' 21 | 'You should be logged in as application user\n> '.format( 22 | client.auth.get_authorize_url())) 23 | except Exception, e: 24 | print "Something went wrong, please check entered keys" 25 | return 26 | 27 | 28 | 29 | print 'Getting tokens' 30 | 31 | try: 32 | access_token, access_token_secret = client.auth.get_access_token(verifier) 33 | except Exception, e: 34 | print "Something went wrong, please check verification code is correct" 35 | return 36 | 37 | try: 38 | client = odesk.Client(public_key, secret_key, 39 | oauth_access_token=access_token, 40 | oauth_access_token_secret=access_token_secret) 41 | except Exception, e: 42 | print "Something went wrong, looks like tokens aren't ok, check them" 43 | return 44 | 45 | print 'Use following information for API usage:' 46 | print 'oauth_access_token: "{0}"'.format(access_token) 47 | print 'oauth_access_token_secret: "{0}"'.format(access_token_secret) 48 | 49 | 50 | if __name__ == '__main__': 51 | client = api_authorization() 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /odesk_jobs.py: -------------------------------------------------------------------------------- 1 | import odesk 2 | from datetime import datetime, timedelta 3 | import mechanize,cookielib 4 | import re 5 | import sys 6 | import json 7 | 8 | class odesk_jobs(NebriOS): 9 | schedule = "0 0 * * *" # daily 10 | 11 | app_key = '' # your oDesk application key 12 | app_secret = '' # oDesk application secret key 13 | 14 | #to get oDesk api tokens you need: 15 | username = '' # your oDesk login 16 | password = '' # your oDesk password 17 | 18 | def login_to_odesk(self, client, br): 19 | # Cookie Jar 20 | cj = cookielib.LWPCookieJar() 21 | br.set_cookiejar(cj) 22 | 23 | # Browser options 24 | br.set_handle_equiv(True) 25 | br.set_handle_gzip(True) 26 | br.set_handle_redirect(True) 27 | br.set_handle_referer(True) 28 | br.set_handle_robots(False) 29 | 30 | # Follows refresh 0 but not hangs on refresh > 0 31 | br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1) 32 | 33 | # Want debugging messages? 34 | #br.set_debug_http(True) 35 | #br.set_debug_redirects(True) 36 | #br.set_debug_responses(True) 37 | 38 | # User-Agent 39 | br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')] 40 | 41 | # Open odesk site 42 | r = br.open('https://www.odesk.com/login.php') 43 | form = br.forms().next() # actually login form name is 'login' but there is only one form 44 | print 'Logging in to oDesk using URL', form.action 45 | form['username'] = self.username 46 | form['password'] = self.password 47 | br.form = form 48 | r = br.submit() 49 | html = r.read() 50 | m = re.match('[^]*(Invalid username, email or password)[^]*', html) 51 | 52 | if m.groups(): 53 | raise RuntimeError, 'Cannot login to oDesk: Invalid username, email or password' 54 | else: 55 | print 'Login successful' 56 | 57 | def get_verifier(self, client, br): 58 | verifier = '' 59 | if client and br: 60 | url = client.auth.get_authorize_url() 61 | print 'Getting verifier using URL', url 62 | r = br.open(url) 63 | html = r.read() 64 | # authorize our application (if needed) 65 | for form in br.forms(): 66 | if form.attrs['id'] == 'authorize': 67 | print "Sending authorize" 68 | br.form = form 69 | r = br.submit() 70 | html = r.read() 71 | break 72 | # check that our verifier is on form and return it 73 | m = re.search('Your oauth_verifier=([a-zA-Z0-9]+)<\/div>', html) 74 | if m: 75 | verifier = m.groups()[0] 76 | print 'Verifier received:', verifier 77 | else: 78 | print "Something is wrong in initialisation: client:", client, "browser:", br 79 | return verifier 80 | 81 | 82 | def authorize(self): 83 | # for details go to https://developers.odesk.com/?lang=python#authentication_oauth-10 84 | client = odesk.Client(public_key=self.app_key, secret_key=self.app_secret) 85 | 86 | # 1. Get request token 87 | request_token, request_token_secret = client.auth.get_request_token() 88 | print 'Received request_token:', request_token, ', request_token_secret:', request_token_secret 89 | 90 | # 2. Authorize and get verifier 91 | # Browser 92 | br = mechanize.Browser() 93 | self.login_to_odesk(client, br) 94 | verifier = self.get_verifier(client, br) 95 | 96 | # 3. Get access token 97 | access_token, access_token_secret = client.auth.get_access_token(verifier) 98 | 99 | # 4. update and save new tokens 100 | if access_token and access_token_secret: 101 | client = odesk.Client(public_key=self.app_key, secret_key=self.app_secret, 102 | oauth_access_token=access_token, oauth_access_token_secret=access_token_secret, 103 | fmt='json') 104 | self.access_token = access_token 105 | self.access_token_secret = access_token_secret 106 | return client 107 | return 108 | 109 | def search_jobs(self, client): 110 | if not client: 111 | print('Client must be initialized before calling job_search') 112 | raise RuntimeError, 'Client is not initialized' 113 | 114 | request_params = {'q':'python', 115 | 'job_status': 'open', 116 | 'days_posted': 5, 117 | 'budget': '1000-', 118 | 'client_hires': '15-', 119 | 'skills':['python'] } 120 | response = client.provider_v2.search_jobs(data=request_params) 121 | if response: 122 | jobs_found = response 123 | return jobs_found 124 | return 125 | 126 | def filter_date_created(self, jobs_found, days_ago=7): 127 | if not jobs_found: 128 | return 129 | jobs_filtered = [] 130 | #get current datetime in utc because server gives us response in utc 131 | week_ago = datetime.utcnow() - timedelta(days=days_ago) 132 | for job in jobs_found: 133 | date_created = datetime.strptime(job[u'date_created'], "%Y-%m-%dT%H:%M:%S+%f") 134 | if (date_created.isoformat() > week_ago.isoformat() ): 135 | jobs_filtered.append(job) 136 | print 'Total jobs found: ', len(jobs_found) 137 | print 'Filtered jobs: ', len(jobs_filtered) 138 | self.jobs_filtered = jobs_filtered 139 | return jobs_filtered 140 | 141 | 142 | def check(self): 143 | # 3 atempts to get acess tokens and create odesk clint 144 | for a in range(3): 145 | # if we have all tokens - just create oDesk Client 146 | if self.access_token and self.access_token_secret: 147 | client = odesk.Client(public_key=self.app_key, secret_key=self.app_secret, 148 | oauth_access_token=self.access_token, oauth_access_token_secret=self.access_token_secret, 149 | fmt='json') 150 | else: #else we have to login and get access tokens 151 | client = self.authorize() 152 | 153 | if not client: continue 154 | 155 | # get jobs from oDesk: 156 | try: 157 | jobs = self.search_jobs(client) 158 | except RuntimeError, err: 159 | print 'Error while searching jobs on oDesk:', err 160 | continue 161 | 162 | if not jobs: continue 163 | 164 | jobs_filtered = self.filter_date_created(jobs) 165 | if not self.jobs_filtered: continue 166 | else: return True 167 | #if during 3 attempts we didn't return True, nothing is found or error occured. 168 | return False 169 | 170 | 171 | def action(self): 172 | if not shared.compatible_odesk_jobs: 173 | shared.compatible_odesk_jobs = [] 174 | if not self.jobs_filtered: 175 | return 176 | 177 | for job in self.jobs_filtered: 178 | shared.number_of_elements_in_list_at_action = len(self.jobs_filtered) 179 | shared.compatible_odesk_jobs.append( {'job' : job['title'], 180 | 'url' : job['url'], 181 | 'date_created': job['date_created']}) 182 | -------------------------------------------------------------------------------- /rssWatcher.py: -------------------------------------------------------------------------------- 1 | import feedparser 2 | 3 | class rssWatcher(NebriOS): 4 | listens_to = ['rss_deal_checker'] 5 | 6 | def check(self): 7 | return True 8 | 9 | def action(self): 10 | #Query the feed 11 | feed = feedparser.parse("http://s1.dlnws.com/dealnews/rss/editors-choice.xml") 12 | 13 | new_last_entry = feed.entries[0]['title'] 14 | 15 | if shared.deal_list2 is None: 16 | shared.deal_list2 = [] 17 | 18 | #iterate the entries 19 | for entry in feed.entries: 20 | #Compare to each input 21 | for inp in shared.rss_inputs: 22 | #Break out of function because we have been to this point in the feed before. 23 | if shared.last_rss_entry == entry['title']: 24 | #Update with the most recent "last_entry" 25 | shared.last_rss_entry = new_last_entry 26 | return 27 | #We make this case insensitive with .lower() 28 | if inp.lower().strip() in entry['title'].lower(): 29 | #update the KVP 30 | print entry['title'] 31 | shared.deal_list2.append(entry['title']) 32 | 33 | #Got through the whole RSS Feed 34 | #Update with the most recent "last_entry" 35 | shared.last_rss_entry = new_last_entry 36 | -------------------------------------------------------------------------------- /slack_connector.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from slackclient import SlackClient # pip install slackclient 4 | 5 | # This script requires that send_slack_message is set to True, 6 | # and that a message and channel are specified. If your message 7 | # has spaces, it will need to be enclosed in quotes. 8 | 9 | token = '' 10 | client = SlackClient(token) 11 | 12 | 13 | class slack_connector(NebriOS): 14 | listens_to = ['send_slack_message'] 15 | 16 | def check(self): 17 | return self.send_slack_message == True 18 | 19 | def action(self): 20 | channel = self.slack_channel 21 | msg = self.slack_message 22 | if channel and msg: 23 | success, channel = get_channel_id(channel) 24 | if success: 25 | client.api_call("chat.postMessage", channel=channel, text=msg) 26 | else: 27 | print "Error: {}".format(channel) 28 | self.send_slack_message = False 29 | 30 | 31 | def get_channel_id(name): 32 | # helper function that get's a channel's ID for use in other API calls 33 | response = client.api_call('channels.list') 34 | response = json.loads(response) 35 | 36 | if response['ok'] == True: 37 | for channel in response['channels']: 38 | if channel['name'] == name: 39 | return True, channel['id'] 40 | return False, "Channel not found" 41 | 42 | return False, response['error'] 43 | -------------------------------------------------------------------------------- /smartthings/README.md: -------------------------------------------------------------------------------- 1 | # Smart Thing NebriOS 2 | 3 | These scripts will allow you to control your smart things devices from NebriOS. 4 | ## Setup 5 | 6 | 1. First, you create an app on smart things using our modified IOTDB bridge app. These scripts can be found in the iotdb folder found [here](https://github.com/adamhub/nebri/tree/master/smartthings/iotdb). 7 | 8 | 2. Give OAuth access to this app from inside SmartThings so NebriOS can communicate with it. More information on setting up these steps can be found here: https://github.com/dpjanes/iotdb-smartthings 9 | 10 | 3. Add the included scripts and forms to your NebriOS instance. Be sure to change relevant information, namely the instance_name in the st_authorize script. 11 | 12 | 13 | ## Running The Flow 14 | 15 | 1. Obtain the OAuth Client ID and Secret for your app from Smart Things. 16 | 2. Enter these values in the St OAuth Keys form in Nebrios. 17 | 3. You should receive an email from NerbiOS with a link to authorize the app. Follow the link and authorize the items you would like.You will be redirected to your instance. It may take a few minutes for your device list to populate; after that, you can fill out the SmartThings Send Command form to send commands to any of your authorized devices. 18 | 19 | A list of commands can be found here: 20 | https://graph.api.smartthings.com/ide/doc/capabilities 21 | 22 | The authorization only needs to be run once, and from that point you can just run commands from the form. 23 | -------------------------------------------------------------------------------- /smartthings/forms/st_oauth_keys.py: -------------------------------------------------------------------------------- 1 | class st_oauth_keys(Form): 2 | form_title = "SmartThings OAuth" 3 | form_instructions = """1) Go to you SmartThings dashboard 4 | 2) Navigate to the IOTDB.bridge SmartApp 5 | 3) Click on App Settings, then on OAuth 6 | 4) Copy the Client ID and Client Secret 7 | 5) Paste them into the form below""" 8 | 9 | st_client_id = String(label="Client ID", required=True) 10 | st_client_secret = String(label="Client Secret", required=True) -------------------------------------------------------------------------------- /smartthings/forms/st_send_command.py: -------------------------------------------------------------------------------- 1 | class st_send_command(Form): 2 | form_title = "Send Command to SmartThings" 3 | form_instructions = "Choose your device and then enter the command:" 4 | 5 | def get_device_names(): 6 | if shared.st_devices: 7 | return [('{"id": "%s", "type": "%s"}' % (device['id'], device['type']), device['label']) for device in shared.st_devices] 8 | return [] 9 | 10 | st_device = String(label="Device Name", choices=get_device_names(), required=True) 11 | st_command = String(label="Command", message="Enter the Command you want to execute. e.g. 'setCoolingSetpoint'", required=True) 12 | st_command_arguments = String(label="Arguments List", message="Enter a LIST of Arguments for the Command using Python List style syntax. e.g. [78]") -------------------------------------------------------------------------------- /smartthings/iotdb/smarthings.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * smartthings.groovy 3 | * 4 | * David Janes 5 | * IOTDB.org 6 | * 2014-02-01 7 | * 8 | * Allow control of your SmartThings via an API; 9 | * Allow monitoring of your SmartThings using 10 | * MQTT through the IOTDB MQTT Bridge. 11 | * 12 | * Follow us on Twitter: 13 | * - @iotdb 14 | * - @dpjanes 15 | * 16 | * A work in progress! 17 | * 18 | * This is for SmartThing's benefit. There's no need to 19 | * change this unless you really want to 20 | */ 21 | 22 | definition( 23 | name: "IOTDB.bridge", 24 | namespace: "", 25 | author: "David Janes", 26 | description: "Bridge to/from JSON/MQTT.", 27 | category: "My Apps", 28 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 29 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png", 30 | oauth: true) 31 | 32 | /* --- IOTDB section --- */ 33 | /* 34 | * IOTDB MQTT Bridge 35 | * - this works for now 36 | * - your life is private 37 | * - just set to empty values to turn off MQTT 38 | * 39 | * The values below can be copied from this page 40 | * - https://iotdb.org/playground/mqtt/bridge 41 | * 42 | * Make sure you are logged into IOTDB first 43 | */ 44 | def _settings() 45 | { 46 | [ 47 | iotdb_api_username: "", 48 | iotdb_api_key: "" 49 | ] 50 | } 51 | 52 | 53 | 54 | /* --- setup section --- */ 55 | /* 56 | * The user 57 | * Make sure that if you change anything related to this in the code 58 | * that you update the preferences in your installed apps. 59 | * 60 | * Note that there's a SmartThings magic that's _required_ here, 61 | * in that you cannot access a device unless you have it listed 62 | * in the preferences. Access to those devices is given through 63 | * the name used here (i.e. d_*) 64 | */ 65 | preferences { 66 | section("Allow IOTDB to Control & Access These Things...") { 67 | input "d_switch", "capability.switch", title: "Switch", multiple: true 68 | input "d_motion", "capability.motionSensor", title: "Motion", required: false, multiple: true 69 | input "d_temperature", "capability.temperatureMeasurement", title: "Temperature", multiple: true 70 | input "d_contact", "capability.contactSensor", title: "Contact", required: false, multiple: true 71 | input "d_acceleration", "capability.accelerationSensor", title: "Acceleration", required: false, multiple: true 72 | input "d_presence", "capability.presenceSensor", title: "Presence", required: false, multiple: true 73 | input "d_battery", "capability.battery", title: "Battery", multiple: true 74 | input "d_threeAxis", "capability.threeAxis", title: "3 Axis", multiple: true 75 | } 76 | } 77 | 78 | /* 79 | input "d_alarm", "capability.alarm", title: "alarm", multiple: true 80 | input "d_configuration", "capability.configuration", title: "configuration", multiple: true 81 | input "d_illuminanceMeasurement", "capability.illuminanceMeasurement", title: "illuminanceMeasurement", multiple: true 82 | input "d_polling", "capability.polling", title: "polling", multiple: true 83 | input "d_relativeHumidityMeasurement", "capability.relativeHumidityMeasurement", title: "relativeHumidityMeasurement", multiple: true 84 | input "d_thermostatCoolingSetpoint", "capability.thermostatCoolingSetpoint", title: "thermostatCoolingSetpoint", multiple: true 85 | input "d_thermostatFanMode", "capability.thermostatFanMode", title: "thermostatFanMode", multiple: true 86 | input "d_thermostatHeatingSetpoint", "capability.thermostatHeatingSetpoint", title: "thermostatHeatingSetpoint", multiple: true 87 | input "d_thermostatMode", "capability.thermostatMode", title: "thermostatMode", multiple: true 88 | input "d_thermostatSetpoint", "capability.thermostatSetpoint", title: "thermostatSetpoint", multiple: true 89 | input "d_threeAxisMeasurement", "capability.threeAxisMeasurement", title: "threeAxisMeasurement", multiple: true 90 | input "d_waterSensor", "capability.waterSensor", title: "waterSensor", multiple: true 91 | 92 | lqi: 100 % 93 | acceleration: inactive 94 | threeAxis: -38,55,1021 95 | battery: 88 % 96 | temperature: 65 F 97 | */ 98 | 99 | /* 100 | * The API 101 | * - ideally the command/update bit would actually 102 | * be a PUT call on the ID to make this restful 103 | */ 104 | mappings { 105 | path("/:type") { 106 | action: [ 107 | GET: "_api_list" 108 | ] 109 | } 110 | path("/:type/:id") { 111 | action: [ 112 | GET: "_api_get", 113 | PUT: "_api_put" 114 | ] 115 | } 116 | } 117 | 118 | /* 119 | * This function is called once when the app is installed 120 | */ 121 | def installed() { 122 | _event_subscribe() 123 | } 124 | 125 | /* 126 | * This function is called every time the user changes 127 | * their preferences 128 | */ 129 | def updated() 130 | { 131 | log.debug "updated" 132 | unsubscribe() 133 | _event_subscribe() 134 | } 135 | 136 | /* --- event section --- */ 137 | 138 | /* 139 | * What events are we interested in. This needs 140 | * to be in it's own function because both 141 | * updated() and installed() are interested in it. 142 | */ 143 | def _event_subscribe() 144 | { 145 | subscribe(d_switch, "switch", "_on_event") 146 | subscribe(d_motion, "motion", "_on_event") 147 | subscribe(d_temperature, "temperature", "_on_event") 148 | subscribe(d_contact, "contact", "_on_event") 149 | subscribe(d_acceleration, "acceleration", "_on_event") 150 | subscribe(d_presence, "presence", "_on_event") 151 | subscribe(d_battery, "battery", "_on_event") 152 | subscribe(d_threeAxis, "threeAxis", "_on_event") 153 | } 154 | 155 | /* 156 | * This function is called whenever something changes. 157 | * Right now it just 158 | */ 159 | def _on_event(evt) 160 | { 161 | // log.debug "_on_event XXX event.id=${evt?.id} event.deviceId=${evt?.deviceId} event.isStateChange=${evt?.isStateChange} event.name=${evt?.name}" 162 | 163 | def dt = _device_and_type_for_event(evt) 164 | if (!dt) { 165 | log.debug "_on_event deviceId=${evt.deviceId} not found?" 166 | return; 167 | } 168 | 169 | def jd = _device_to_json(dt.device, dt.type) 170 | log.debug "_on_event deviceId=${jd}" 171 | 172 | _send_mqtt(dt.device, dt.type, jd) 173 | 174 | } 175 | 176 | /* --- API section --- */ 177 | def _api_list() 178 | { 179 | _devices_for_type(params.type).collect{ 180 | _device_to_json(it, params.type) 181 | } 182 | } 183 | 184 | def _api_put() 185 | { 186 | def devices = _devices_for_type(params.type) 187 | def device = devices.find { it.id == params.id } 188 | if (!device) { 189 | httpError(404, "Device not found") 190 | } else { 191 | _device_command(device, params.type, request.JSON) 192 | } 193 | } 194 | 195 | def _api_get() 196 | { 197 | def devices = _devices_for_type(params.type) 198 | def device = devices.find { it.id == params.id } 199 | if (!device) { 200 | httpError(404, "Device not found") 201 | } else { 202 | _device_to_json(device, type) 203 | } 204 | } 205 | 206 | void _api_update() 207 | { 208 | _do_update(_devices_for_type(params.type), params.type) 209 | } 210 | 211 | /* 212 | * I don't know what this does but it seems to be needed 213 | */ 214 | def deviceHandler(evt) { 215 | } 216 | 217 | /* --- communication section: not done --- */ 218 | 219 | /* 220 | * An example of how to get PushingBox.com to send a message 221 | */ 222 | def _send_pushingbox() { 223 | log.debug "_send_pushingbox called"; 224 | 225 | def devid = "XXXX" 226 | def messageText = "Hello_World" 227 | 228 | httpGet("http://api.pushingbox.com/pushingbox?devid=${devid}&message=xxx_xxx") 229 | } 230 | 231 | /* 232 | * Send information the the IOTDB MQTT Bridge 233 | * See https://iotdb.org/playground/mqtt/bridge for documentation 234 | */ 235 | def _send_mqtt(device, device_type, deviced) { 236 | def settings = _settings() 237 | // log.debug "_send_mqtt: iotdb_api_username=${settings.iotdb_api_username},iotdb_api_key=${settings.iotdb_api_key}" 238 | 239 | if (!settings.iotdb_api_username || !settings.iotdb_api_key) { 240 | return 241 | } 242 | 243 | log.debug "_send_mqtt called"; 244 | 245 | def now = Calendar.instance 246 | def date = now.time 247 | def millis = date.time 248 | def sequence = millis 249 | def isodatetime = deviced?.value?.timestamp 250 | 251 | def digest = "${settings.iotdb_api_key}/${settings.iotdb_api_username}/${isodatetime}/${sequence}".toString(); 252 | def hash = digest.encodeAsMD5(); 253 | 254 | def topic = "st/${device_type}/${deviced.id}".toString() 255 | 256 | def uri = "https://iotdb.org/playground/mqtt/bridge" 257 | def headers = [:] 258 | def body = [ 259 | "topic": topic, 260 | "payloadd": deviced?.value, 261 | "timestamp": isodatetime, 262 | "sequence": sequence, 263 | "signed": hash, 264 | "username": settings.iotdb_api_username 265 | ] 266 | 267 | def params = [ 268 | uri: uri, 269 | headers: headers, 270 | body: body 271 | ] 272 | 273 | log.debug "_send_mqtt: params=${params}" 274 | httpPutJson(params) { log.debug "_send_mqtt: response=${response}" } 275 | } 276 | 277 | 278 | 279 | /* --- internals --- */ 280 | /* 281 | * Devices and Types Dictionary 282 | */ 283 | def _dtd() 284 | { 285 | [ 286 | switch: d_switch, 287 | motion: d_motion, 288 | temperature: d_temperature, 289 | contact: d_contact, 290 | acceleration: d_acceleration, 291 | presence: d_presence, 292 | battery: d_battery, 293 | threeAxis: d_threeAxis 294 | ] 295 | } 296 | 297 | def _devices_for_type(type) 298 | { 299 | _dtd()[type] 300 | } 301 | 302 | def _device_and_type_for_event(evt) 303 | { 304 | def dtd = _dtd() 305 | 306 | for (dt in _dtd()) { 307 | if (dt.key != evt.name) { 308 | continue 309 | } 310 | 311 | def devices = dt.value 312 | for (device in devices) { 313 | if (device.id == evt.deviceId) { 314 | return [ device: device, type: dt.key ] 315 | } 316 | } 317 | } 318 | } 319 | 320 | /** 321 | * Do a device command 322 | */ 323 | private _device_command(device, type, jsond) { 324 | if (!device) { 325 | log.debug "No device" 326 | return; 327 | } 328 | if (!jsond) { 329 | log.debug "No JSON" 330 | return; 331 | } 332 | 333 | /** 334 | * if (type == "switch") { 335 | * def n = jsond['switch'] 336 | * if (n == -1) { 337 | * def o = device.currentState('switch')?.value 338 | * n = ( o != 'on' ) 339 | * } 340 | * if (n) { 341 | * device.on() 342 | * } else { 343 | * device.off() 344 | * } 345 | * } else { 346 | * log.debug "_device_command: device type=${type} doesn't take commands" 347 | * } 348 | */ 349 | 350 | def command = jsond['command'] 351 | def arguments = jsond['arguments'] 352 | 353 | if (command) { 354 | if (arguments) { 355 | device."$command"(*arguments) 356 | log.debug "Command triggered: ${command} with the arguments: ${arguments}" 357 | } else { 358 | device."$command"() 359 | log.debug "Command triggered: ${command} with NO ARGUMENTS" 360 | } 361 | } else { 362 | log.debug "Missing command argument" 363 | } 364 | } 365 | 366 | /* 367 | * Convert a single device into a JSONable object 368 | */ 369 | private _device_to_json(device, type) { 370 | if (!device) { 371 | return; 372 | } 373 | 374 | def vd = [:] 375 | def jd = [id: device.id, name: device.name, label: device.label, type: type, value: vd]; 376 | 377 | if (type == "switch") { 378 | def s = device.currentState('switch') 379 | vd['timestamp'] = s?.isoDate 380 | vd['switch'] = s?.value == "on" 381 | } else if (type == "motion") { 382 | def s = device.currentState('motion') 383 | vd['timestamp'] = s?.isoDate 384 | vd['motion'] = s?.value == "active" 385 | } else if (type == "temperature") { 386 | def s = device.currentState('temperature') 387 | vd['timestamp'] = s?.isoDate 388 | vd['temperature'] = s?.value.toFloat() 389 | } else if (type == "contact") { 390 | def s = device.currentState('contact') 391 | vd['timestamp'] = s?.isoDate 392 | vd['contact'] = s?.value == "closed" 393 | } else if (type == "acceleration") { 394 | def s = device.currentState('acceleration') 395 | vd['timestamp'] = s?.isoDate 396 | vd['acceleration'] = s?.value == "active" 397 | } else if (type == "presence") { 398 | def s = device.currentState('presence') 399 | vd['timestamp'] = s?.isoDate 400 | vd['presence'] = s?.value == "present" 401 | } else if (type == "battery") { 402 | def s = device.currentState('battery') 403 | vd['timestamp'] = s?.isoDate 404 | vd['battery'] = s?.value.toFloat() / 100.0; 405 | } else if (type == "threeAxis") { 406 | def s = device.currentState('threeAxis') 407 | vd['timestamp'] = s?.isoDate 408 | vd['x'] = s?.xyzValue?.x 409 | vd['y'] = s?.xyzValue?.y 410 | vd['z'] = s?.xyzValue?.z 411 | } 412 | 413 | return jd 414 | } 415 | 416 | -------------------------------------------------------------------------------- /smartthings/libraries/smartthings.py: -------------------------------------------------------------------------------- 1 | # 2 | # smartthings.py 3 | # 4 | # David Janes 5 | # IOTDB.org 6 | # 2014-01-31 7 | # 8 | # Demonstrate how to use the SmartThings API from Python. 9 | # 10 | # See also: 11 | # Example App explanation: 12 | # http://build.smartthings.com/blog/tutorial-creating-a-custom-rest-smartapp-endpoint/ 13 | # 14 | # Example PHP code: 15 | # https://www.dropbox.com/s/7m7gmlr9q3u7rmk/exampleOauth.php 16 | # 17 | # Example "Groovy"/SMART code (this is the app we tap into) 18 | # https://www.dropbox.com/s/lohzziy2wjlrppb/endpointExample.groovy 19 | # 20 | 21 | # import sys 22 | import requests 23 | # import pprint 24 | import json 25 | 26 | ## import httplib 27 | ## httplib.HTTPConnection.debuglevel = 1 28 | 29 | # from optparse import OptionParser 30 | 31 | # try: 32 | # import iotdb_log 33 | # except: 34 | # class iotdb_log(object): 35 | # @staticmethod 36 | # def log(**ad): 37 | # pprint.pprint(ad) 38 | 39 | class SmartThings(object): 40 | def __init__(self, verbose=True): 41 | self.verbose = verbose 42 | self.std = {} 43 | self.endpointd = {} 44 | self.deviceds = {} 45 | 46 | def load_settings(self, filename="smartthings.json"): 47 | """Load the JSON Settings file. 48 | 49 | See the documentation, but briefly you can 50 | get it from here: 51 | https://iotdb.org/playground/oauthorize 52 | """ 53 | 54 | with open(filename) as fin: 55 | self.std = json.load(fin) 56 | 57 | def request_endpoints(self): 58 | """Get the endpoints exposed by the SmartThings App 59 | 60 | The first command you need to call 61 | """ 62 | 63 | endpoints_url = self.std["api"] 64 | endpoints_paramd = { 65 | "access_token": self.std["access_token"] 66 | } 67 | 68 | endpoints_response = requests.get(url=endpoints_url, params=endpoints_paramd) 69 | self.endpointd = endpoints_response.json()[0] 70 | 71 | # if self.verbose: iotdb_log.log( 72 | # "endpoints", 73 | # endpoints_url=endpoints_url, 74 | # endpoints_paramd=endpoints_paramd, 75 | # resultds=self.endpointd, 76 | # ) 77 | 78 | def request_devices(self, device_type): 79 | """List the devices""" 80 | 81 | devices_url = "https://graph.api.smartthings.com%s/%s" % ( self.endpointd["url"], device_type, ) 82 | devices_paramd = { 83 | } 84 | devices_headerd = { 85 | "Authorization": "Bearer %s" % self.std["access_token"], 86 | } 87 | 88 | devices_response = requests.get(url=devices_url, params=devices_paramd, headers=devices_headerd) 89 | self.deviceds = devices_response.json() 90 | for switchd in self.deviceds: 91 | switchd['url'] = "%s/%s" % ( devices_url, switchd['id'], ) 92 | 93 | # if self.verbose: iotdb_log.log( 94 | # "devices", 95 | # url=devices_url, 96 | # paramd=devices_paramd, 97 | # deviceds=self.deviceds, 98 | # ) 99 | 100 | return self.deviceds 101 | 102 | def device_request(self, deviced, requestd): 103 | """Send a request the named device""" 104 | 105 | command_url = deviced['url'] 106 | command_paramd = { 107 | "access_token": self.std["access_token"] 108 | } 109 | command_headerd = {} 110 | 111 | command_response = requests.put( 112 | url=command_url, 113 | params=command_paramd, 114 | headers=command_headerd, 115 | data=json.dumps(requestd) 116 | ) 117 | 118 | return "I got this far!" 119 | 120 | 121 | # if __name__ == '__main__': 122 | # dtypes = [ 123 | # "switch", "motion", "presence", "acceleration", "contact", 124 | # "temperature", "battery", "acceleration", "threeAxis", 125 | # ] 126 | # 127 | # parser = OptionParser() 128 | # parser.add_option( 129 | # "", "--debug", 130 | # default = False, 131 | # action = "store_true", 132 | # dest = "debug", 133 | # help = "", 134 | # ) 135 | # parser.add_option( 136 | # "", "--verbose", 137 | # default = False, 138 | # action = "store_true", 139 | # dest = "verbose", 140 | # help = "", 141 | # ) 142 | # parser.add_option( 143 | # "", "--type", 144 | # dest = "device_type", 145 | # help = "The device type (required), one of %s" % ", ".join(dtypes) 146 | # ) 147 | # parser.add_option( 148 | # "", "--id", 149 | # dest = "device_id", 150 | # help = "The ID or Name of the device to manipulate" 151 | # ) 152 | # parser.add_option( 153 | # "", "--request", 154 | # dest = "request", 155 | # help = "Something to do, e.g. 'switch=1', 'switch=0'" 156 | # ) 157 | # 158 | # (options, args) = parser.parse_args() 159 | # 160 | # #if not options.device_type: 161 | # #print >> sys.stderr, "%s: --type <%s>" % ( sys.argv[0], "|".join(dtypes)) 162 | # #parser.print_help(sys.stderr) 163 | # #sys.exit(1) 164 | # 165 | # 166 | # st = SmartThings(verbose=options.verbose) 167 | # st.load_settings() 168 | # st.request_endpoints() 169 | # 170 | # ds = st.request_devices(options.device_type) 171 | # 172 | # if options.device_id: 173 | # ds = filter(lambda d: options.device_id in [ d.get("id"), d.get("label"), ], ds) 174 | # 175 | # if options.request: 176 | # key, value = options.request.split('=', 2) 177 | # try: 178 | # value = int(value) 179 | # except ValueError: 180 | # pass 181 | # 182 | # requestd = { 183 | # key: value 184 | # } 185 | # 186 | # for d in ds: 187 | # # iotdb_log.log(device=d, request=requestd) 188 | # st.device_request(d, requestd) 189 | # 190 | # else: 191 | # print json.dumps(ds, indent=2, sort_keys=True) 192 | -------------------------------------------------------------------------------- /smartthings/scripts/st_acces_token.py: -------------------------------------------------------------------------------- 1 | class st_acces_token(NebriOS): 2 | listens_to = ['st_access_token'] 3 | 4 | def check(self): 5 | return self.st_access_token 6 | 7 | def action(self): 8 | shared.st_access_token = self.st_access_token 9 | self.st_get_devices = True -------------------------------------------------------------------------------- /smartthings/scripts/st_authorize.py: -------------------------------------------------------------------------------- 1 | class st_authorize(NebriOS): 2 | listens_to = ['shared.st_client_id', 'shared.st_client_secret', 'shared.st_oauth_email', 'st_authorize'] 3 | 4 | def check(self): 5 | return shared.st_client_id and shared.st_client_secret and shared.st_oauth_email 6 | 7 | 8 | def action(self): 9 | send_email(shared.st_oauth_email, 10 | """ 11 | Please authorize your SmartThings App by following this link: 12 | 13 | http://oauth.nebrios.com/smartthings/oauth/authorize/?client_id={{ shared.st_client_id }}&client_secret={{ shared.st_client_secret }}&instance_inbox=risethink@nebrios.com&instance_name=risethink&sender={{ last_actor }} 14 | 15 | Thank you. 16 | """, 17 | subject="Authorize SmartThings App", 18 | attach_variables=False) -------------------------------------------------------------------------------- /smartthings/scripts/st_example.py: -------------------------------------------------------------------------------- 1 | class st_example(NebriOS): 2 | listens_to = ['st_example'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | 9 | # The Script st_send_commands have been built as a wrapper to the SmartThings Hub 10 | # It takes 2 Required KVPs plus 1 optional KVP 11 | # Simply populate the KVPs will the necessary values and it will take it from there 12 | 13 | # First, set self.st_device_label to the Label of the device you want to control 14 | # Note that in SmartThings this is the Label and not the Name 15 | 16 | self.st_device_label = "Office Thermostat" 17 | 18 | # Next, store the specific device command to run into "self.st_command" 19 | 20 | self.st_command = "setCoolingSetpoint" 21 | 22 | # We are sending the command "setCoolingSetpoint" to our Thermostat 23 | 24 | # A full list of built-in commands are available here: https://graph.api.smartthings.com/ide/doc/capabilities 25 | # Custom device-types can also be triggered by simply using the appropriate command names 26 | 27 | # If your command accepts arguments, such as our command used above, 28 | # add the arguments to a List and save to self.st_command_arguments 29 | 30 | # This KVP is optional 31 | 32 | self.st_command_arguments = [78] 33 | 34 | 35 | # All together, setting these 3 KVPs will cause st_send_command to tell our Thermostat to 36 | # beging cooling the office to 78 degrees -------------------------------------------------------------------------------- /smartthings/scripts/st_get_devices.py: -------------------------------------------------------------------------------- 1 | import smartthings 2 | 3 | class st_get_devices(NebriOS): 4 | listens_to = ['st_get_devices'] 5 | 6 | def check(self): 7 | return True 8 | 9 | def action(self): 10 | # TODO: loop through list of device types 11 | # TODO: loop through each device 12 | # TODO: create dictionary for each device 13 | # TODO: save list of dictionaries for each device to shared 14 | st = smartthings.SmartThings(verbose=True) 15 | st.std = { 16 | 'access_token': shared.st_access_token, 17 | 'api': 'https://graph.api.smartthings.com/api/smartapps/endpoints/%s/' % shared.st_client_id, 18 | 'api_location': 'graph.api.smartthings.com', 19 | 'client_id': shared.st_client_id, 20 | 'client_secret': shared.st_client_secret, 21 | 'scope': 'app', 22 | 'expires_in': 1576799999, 23 | 'token_type': 'bearer', 24 | } 25 | 26 | st.request_endpoints() 27 | 28 | device_type = ['switch', 'motion', 'temperature', 'contact', 'acceleration', 'presence', 'battery', 'threeAxis'] 29 | 30 | shared.st_devices = [] 31 | 32 | for type in device_type: 33 | ds = st.request_devices(type) 34 | for d in ds: 35 | shared.st_devices.append({ 36 | 'type': d['type'], 37 | 'id': d['id'], 38 | 'label': d['label'] if d['label'] else d['name'], 39 | }); -------------------------------------------------------------------------------- /smartthings/scripts/st_save_oauth_keys.py: -------------------------------------------------------------------------------- 1 | class st_save_oauth_keys(NebriOS): 2 | listens_to = ['st_client_id', 'st_client_secret'] 3 | 4 | def check(self): 5 | if self.st_client_id and self.st_client_secret: 6 | return True 7 | else: 8 | return False 9 | 10 | def action(self): 11 | self.st_oauth_status = "New Keys Set: %s" % datetime.now() 12 | 13 | shared.st_client_id = self.st_client_id 14 | shared.st_client_secret = self.st_client_secret 15 | shared.st_oauth_email = self.last_actor -------------------------------------------------------------------------------- /smartthings/scripts/st_send_command.py: -------------------------------------------------------------------------------- 1 | import smartthings 2 | import json 3 | 4 | class st_send_command(NebriOS): 5 | listens_to = ['st_command'] 6 | 7 | def check(self): 8 | if self.st_command and self.st_device_label and shared.st_access_token: 9 | return True 10 | elif not shared.st_access_token: 11 | send_email(self.last_actor, 12 | """Access not setup yet. Use this form to get connected: 13 | 14 | {{ forms.st_oauth_keys }} 15 | 16 | -Nebri""", subject="Need Access", attach_variables=False) 17 | return False 18 | else: 19 | return False 20 | 21 | 22 | def action(self): 23 | 24 | self.st_status = "Ran Command -- %s -- with arguments -- %s -- on device label -- %s -- at: %s" % (self.st_command, self.st_command_arguments, self.st_device_label, datetime.now()) 25 | 26 | st = self.st_api_connection() 27 | 28 | if not shared.st_devices: 29 | self.st_get_devices(st) 30 | 31 | for device in shared.st_devices: 32 | if device['label'] == self.st_device_label: 33 | self.st_device = {"id": device['id'], "type": device['type'], "label": device['label']} 34 | break 35 | 36 | 37 | ds = st.request_devices(self.st_device['type']) 38 | 39 | ds = filter(lambda d: self.st_device['id'] in [ d.get("id"), d.get("label"), ], ds) 40 | 41 | requestd = {"command": self.st_command} 42 | 43 | if self.st_command_arguments: 44 | requestd["arguments"] = self.st_command_arguments 45 | 46 | for d in ds: 47 | self.st_response = st.device_request(d, requestd) 48 | # This is a loop cause I'm guessing there's a way to interact with multiple Devices, 49 | # but for now, this should only ever have 1 device in the "ds" list 50 | 51 | 52 | def st_api_connection(self): 53 | st = smartthings.SmartThings(verbose=True) 54 | st.std = { 55 | 'access_token': shared.st_access_token, 56 | 'api': 'https://graph.api.smartthings.com/api/smartapps/endpoints/%s/' % shared.st_client_id, 57 | 'api_location': 'graph.api.smartthings.com', 58 | 'client_id': shared.st_client_id, 59 | 'client_secret': shared.st_client_secret, 60 | 'scope': 'app', 61 | 'expires_in': 1576799999, 62 | 'token_type': 'bearer', 63 | } 64 | st.request_endpoints() 65 | return st 66 | 67 | 68 | def st_get_devices(self, st): 69 | device_type = ['switch', 'motion', 'temperature', 'contact', 'acceleration', 'presence', 'battery', 'threeAxis'] 70 | 71 | shared.st_devices = [] 72 | 73 | for type in device_type: 74 | ds = st.request_devices(type) 75 | for d in ds: 76 | shared.st_devices.append({ 77 | 'type': d['type'], 78 | 'id': d['id'], 79 | 'label': d['label'] if d['label'] else d['name'], 80 | }); -------------------------------------------------------------------------------- /standup/forms/standup_controller_starter.py: -------------------------------------------------------------------------------- 1 | class standup_controller_starter(Form): 2 | form_title = "Manually Run the Standup Controller" 3 | form_instructions = "Simply submit this Form to get the Standup Controller to go" 4 | 5 | standup_controller_go = Boolean(initial=True) -------------------------------------------------------------------------------- /standup/forms/standup_dev_form.py: -------------------------------------------------------------------------------- 1 | class standup_dev_form(Form): 2 | form_title = "Standup Form" 3 | form_instructions = "Submit your 3 Standup Questions for the standup today." 4 | 5 | question_1 = String(label="What did you work on last?", required=True) 6 | question_2 = String(label="What will you work on next?", required=True) 7 | question_3 = String(label="Is there anything blocking you from being productive?", required=True) 8 | 9 | standup_dev_form_submission_time = DateTime(initial=datetime.now(), hidden=True) -------------------------------------------------------------------------------- /standup/forms/standup_lookup_existing_for_editing.py: -------------------------------------------------------------------------------- 1 | class standup_lookup_existing_for_editing(Form): 2 | form_title = "Lookup Existing Standup" 3 | form_instructions = "Enter the Project Name of the Standup you would like to Edit" 4 | 5 | def get_project_names(): 6 | projects = [] 7 | for standup in shared.standup_roster: 8 | projects.append((standup["project_name"], standup["project_name"])) 9 | return projects 10 | 11 | standup_project_name_lookup = String(label="Project Name", choices=get_project_names(), required=True) 12 | -------------------------------------------------------------------------------- /standup/forms/standup_reset_roster.py: -------------------------------------------------------------------------------- 1 | class standup_reset_roster(Form): 2 | form_title = "Reset the Roster for Testing" 3 | form_instructions = "Just simply submit the form" 4 | 5 | standup_reset_roster = Boolean(initial=True) -------------------------------------------------------------------------------- /standup/forms/standup_schedule_edit.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class standup_schedule_edit(Form): 4 | form_title = "Edit Standup Schedule" 5 | form_instructions = "Enter information regarding the Standup to Edit." 6 | 7 | def validate_email(form, field, email_address): 8 | if re.match(r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"'r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', email_address, re.IGNORECASE): 9 | return True 10 | raise ValidationError('Please provide a valid email address (Example = user@test.com)') 11 | 12 | 13 | standup_project_name_edit = String(label="Project Name", required=True) 14 | standup_time_edit = Time(required=True) 15 | standup_project_manager_edit = String(label="Project Manager", required=True) 16 | standup_project_manager_email_edit = String(label="Project Manager Email", required=True) 17 | 18 | standup_dev_1_name_edit = String(label="Developer Name #1", required=True) 19 | standup_dev_1_email_edit = String(label="Developer Email #1", message="Use additional developer fields as necessary", required=True) 20 | standup_dev_2_name_edit = String(label="Developer Name #2") 21 | standup_dev_2_email_edit = String(label="Developer Email #2") 22 | standup_dev_3_name_edit = String(label="Developer Name #3") 23 | standup_dev_3_email_edit = String(label="Developer Email #3") 24 | standup_dev_4_name_edit = String(label="Developer Name #4") 25 | standup_dev_4_email_edit = String(label="Developer Email #4") 26 | 27 | standup_schedule_edit = Boolean(initial=True, hidden=True) -------------------------------------------------------------------------------- /standup/forms/standup_schedule_new.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class standup_schedule_new(Form): 4 | form_title = "New Standup Schedule" 5 | form_instructions = "Enter information regarding new Standup to schedule." 6 | 7 | 8 | def validate_email(form, field, email_address): 9 | if re.match(r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"'r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', email_address, re.IGNORECASE): 10 | return True 11 | raise ValidationError('Please provide a valid email address (Example = user@test.com)') 12 | 13 | 14 | standup_project_name = String(label="Project Name", message="To edit existing Project, use Edit Standup Schedule form", required=True) 15 | standup_time = Time(required=True) 16 | standup_project_manager = String(label="Project Manager", required=True) 17 | standup_project_manager_email = String(label="Project Manager Email", required=True) 18 | 19 | standup_dev_1_name = String(label="Developer Name #1", required=True) 20 | standup_dev_1_email = String(label="Developer Email #1", message="Use additional developer fields as necessary", required=True) 21 | standup_dev_2_name = String(label="Developer Name #2") 22 | standup_dev_2_email = String(label="Developer Email #2") 23 | standup_dev_3_name = String(label="Developer Name #3") 24 | standup_dev_3_email = String(label="Developer Email #3") 25 | standup_dev_4_name = String(label="Developer Name #4") 26 | standup_dev_4_email = String(label="Developer Email #4") 27 | 28 | standup_schedule_new = Boolean(initial=True, hidden=True) -------------------------------------------------------------------------------- /standup/scripts/standup_all_dev_forms_submitted.py: -------------------------------------------------------------------------------- 1 | class standup_all_dev_forms_submitted(NebriOS): 2 | listens_to = ['child.standup_dev_form_submission_time'] 3 | 4 | def check(self): 5 | for kid in children: 6 | if kid.standup_dev_form_submission_time is None: 7 | return False 8 | 9 | #return False ## Use this to kill the loops 10 | 11 | return True 12 | 13 | def action(self): 14 | self.active_standup_instance_status = "complete" 15 | 16 | for standup in shared.standup_roster: 17 | if standup["project_name"] == self.active_standup_project_name: 18 | standup["status"] = "idle" 19 | 20 | for late_standup in shared.late_standups[:]: 21 | if late_standup["standup_pid"] == self.PROCESS_ID: 22 | shared.late_standups.remove(late_standup) 23 | 24 | recipients = self.active_standup_project_manager_email 25 | for dev in self.active_standup_developers: 26 | recipients += ", %s" % dev["dev_email"] 27 | 28 | body = """Hello %(project_name)s team, 29 | 30 | Here are the submitted standup answers for today: 31 | 32 | """ % {'project_name': self.active_standup_project_name} 33 | 34 | for form in children: 35 | body += """%(dev_name)s: 36 | 37 | 1) %(question_1)s 38 | 2) %(question_2)s 39 | 3) %(question_3)s 40 | 41 | """ % {'dev_name': form.standup_dev_form_name, 'question_1': form.question_1, 'question_2': form.question_2, 'question_3': form.question_3} 42 | 43 | body += """Thank you for all submitting your standup forms on time! 44 | 45 | Our standup is scheduled to begin at %(standup_time)s. 46 | 47 | 48 | Thanks, 49 | 50 | -%(project_manager)s 51 | """ % {'standup_time': self.active_standup_time, 'project_manager': self.active_standup_project_manager} 52 | 53 | send_email(recipients, body, subject="Standup Answers - %s" % self.active_standup_project_name, attach_variables=False) -------------------------------------------------------------------------------- /standup/scripts/standup_controller_master.py: -------------------------------------------------------------------------------- 1 | class standup_controller_master(NebriOS): 2 | listens_to = ['standup_controller_go', 'standup_controller_loop_hack'] 3 | ## Dripped every 5 minutes 4 | 5 | def check(self): 6 | #return False ## Use this to kill the loops 7 | 8 | return True 9 | 10 | ## Let's do all the logic in the Action 11 | 12 | 13 | def action(self): 14 | 15 | self.status = "RAN" 16 | 17 | for standup in shared.standup_roster: 18 | 19 | if (standup["status"] != "active" 20 | and parse_datetime(standup["standup_time"]) - datetime.now() <= timedelta(minutes=30) 21 | and (not "last_run" in standup or parse_datetime(standup["last_run"]).date() != datetime.now().date())): 22 | 23 | ## This must be date-agnostic. It must only care about the time. 24 | 25 | standup["status"] = "active" 26 | standup["last_run"] = datetime.now() 27 | 28 | new_child.active_standup_instance_status = "pending" 29 | new_child.active_standup_project_name = standup["project_name"] 30 | new_child.active_standup_time = standup["standup_time"] 31 | new_child.active_standup_project_manager = standup["project_manager"] 32 | new_child.active_standup_project_manager_email = standup["project_manager_email"] 33 | new_child.active_standup_developers = standup["developers"] 34 | new_child.active_standup_dev_loop_hack = len(standup["developers"]) 35 | new_child.active_standup_dev_loop_hack_initial = len(standup["developers"]) 36 | 37 | if not self.projects_activated: 38 | self.projects_activated = [] 39 | 40 | self.projects_activated.append({"project_name":standup["project_name"], "pid":"Unknown"}) 41 | 42 | 43 | if self.standup_controller_loop_hack: 44 | self.standup_controller_loop_hack += 1 45 | else: 46 | self.standup_controller_loop_hack = 1 47 | 48 | break 49 | ## This break stops the script after the first match in the loop, 50 | ## and basically allows the script to restart the for loop within a new 'instance', 51 | ## so that the new_child functions work -------------------------------------------------------------------------------- /standup/scripts/standup_controller_per_instance.py: -------------------------------------------------------------------------------- 1 | class standup_controller_per_instance(NebriOS): 2 | listens_to = ['active_standup_project_name', 'active_standup_dev_loop_hack'] 3 | 4 | def check(self): 5 | #return False ## Use this to kill the loops 6 | 7 | if self.active_standup_dev_loop_hack > 0: 8 | return True 9 | 10 | def action(self): 11 | 12 | if self.active_standup_dev_loop_hack == self.active_standup_dev_loop_hack_initial: 13 | for project in parent.projects_activated: 14 | if project["project_name"] == self.active_standup_project_name: 15 | project["pid"] = self.PROCESS_ID 16 | 17 | if not self.dev_forms_activated: 18 | self.dev_forms_activated = [] 19 | 20 | for dev in self.active_standup_developers: 21 | if "form_status" not in dev: 22 | 23 | dev["form_status"] = "active" 24 | 25 | new_child.standup_dev_form_name = dev["dev_name"] 26 | new_child.standup_dev_form_email = dev["dev_email"] 27 | new_child.standup_dev_form_status = "pending" 28 | new_child.active_standup_project_name = self.active_standup_project_name 29 | new_child.active_standup_time = self.active_standup_time 30 | new_child.active_standup_project_manager = self.active_standup_project_manager 31 | new_child.active_standup_project_manager_email = self.active_standup_project_manager_email 32 | 33 | 34 | self.dev_forms_activated.append(dev) 35 | 36 | self.active_standup_dev_loop_hack -= 1 37 | 38 | break -------------------------------------------------------------------------------- /standup/scripts/standup_dev_form_submitted.py: -------------------------------------------------------------------------------- 1 | class standup_dev_form_submitted(NebriOS): 2 | listens_to = ['standup_dev_form_submission_time'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | self.standup_dev_form_status = "submitted" 9 | 10 | # Do something... send something to someone... not sure -------------------------------------------------------------------------------- /standup/scripts/standup_dev_forms_late.py: -------------------------------------------------------------------------------- 1 | class standup_dev_forms_late(NebriOS): 2 | #listens_to = ['standup_dev_forms_late_manual'] 3 | 4 | schedule = "*/5 * * * *" 5 | 6 | def check(self): 7 | if self.active_standup_instance_status == "pending" and datetime.now() >= self.active_standup_time: # - timedelta(days=1): 8 | return True 9 | else: 10 | return False 11 | 12 | def action(self): 13 | 14 | self.active_standup_instance_status = "late" 15 | 16 | if shared.late_standups is None: 17 | shared.late_standups = [] 18 | 19 | late_standup = {'standup_project_name': self.active_standup_project_name, 20 | 'standup_datetime': self.active_standup_time, 21 | 'standup_pid': self.PROCESS_ID, 'late_devs': []} 22 | 23 | for kid in children: 24 | if kid.standup_dev_form_status != "submitted": 25 | 26 | late_standup["late_devs"].append({'dev_name': kid.standup_dev_form_name, 27 | 'dev_email': kid.standup_dev_form_email, 28 | 'dev_form_pid': kid.PROCESS_ID}) 29 | 30 | send_email(self.active_standup_project_manager_email, 31 | """Hello %(active_standup_project_manager)s, 32 | 33 | %(kid_standup_dev_form_name)s is late submitting their Standup for %(kid_active_standup_project_name)s. 34 | 35 | Please handle this. 36 | 37 | 38 | Regards, 39 | 40 | -Nebri""" % {'active_standup_project_manager': self.active_standup_project_manager, 41 | 'kid_standup_dev_form_name': kid.standup_dev_form_name, 42 | 'kid_active_standup_project_name': kid.active_standup_project_name}, 43 | 44 | subject="Late Standup: %(dev_name)s - %(project_name)s" % {'dev_name': kid.standup_dev_form_name, 45 | 'project_name': kid.active_standup_project_name}, 46 | attach_variables=False) 47 | 48 | 49 | send_email(kid.standup_dev_form_email, 50 | """Hello %(kid_standup_dev_form_name)s, 51 | 52 | Your standup submission for %(kid_active_standup_project_name)s is late. 53 | 54 | Please submit your standup information as soon as possible. 55 | 56 | 57 | Thank you, 58 | 59 | -Nebri""" % {'kid_standup_dev_form_name': kid.standup_dev_form_name, 60 | 'kid_active_standup_project_name': kid.active_standup_project_name}, 61 | 62 | subject="Late Standup: %(project_name)s" % {'project_name': kid.active_standup_project_name}, 63 | attach_variables=False) 64 | 65 | 66 | shared.late_standups.append(late_standup) -------------------------------------------------------------------------------- /standup/scripts/standup_load_dev_form.py: -------------------------------------------------------------------------------- 1 | class standup_load_dev_form(NebriOS): 2 | listens_to = ['standup_dev_form_name'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | self.standup_dev_form_status = "sent" 9 | 10 | for dev_form in parent.dev_forms_activated: 11 | if dev_form["dev_name"] == self.standup_dev_form_name: 12 | dev_form["pid"] = self.PROCESS_ID 13 | 14 | send_email(self.standup_dev_form_email, 15 | """Hello {{standup_dev_form_name}}, 16 | 17 | The {{active_standup_project_name}} standup meeting with {{active_standup_project_manager}} is schedule to start at {{active_standup_time}}. 18 | 19 | Please submit your 3 Standup Questions to this form before the meeting begins. 20 | 21 | {{forms.standup_dev_form}} 22 | 23 | Thank you. 24 | """, 25 | subject="Standup Form: %s" % self.active_standup_project_name, 26 | attach_variables=False) -------------------------------------------------------------------------------- /standup/scripts/standup_load_edit_form.py: -------------------------------------------------------------------------------- 1 | class standup_load_edit_form(NebriOS): 2 | listens_to = ['standup_project_name_lookup'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | if shared.standup_roster: 9 | for standup in shared.standup_roster: 10 | if standup["project_name"] == self.standup_project_name_lookup: 11 | 12 | self.standup_project_name_edit = standup["project_name"] 13 | self.standup_time_edit = standup["standup_time"] 14 | self.standup_project_manager_edit = standup["project_manager"] 15 | self.standup_project_manager_email_edit = standup["project_manager_email"] 16 | 17 | # This mess is because Forms don't handle Dictionaries :/ 18 | 19 | self.standup_dev_1_name_edit = standup["developers"][0]["dev_name"] 20 | self.standup_dev_1_email_edit = standup["developers"][0]["dev_email"] 21 | 22 | try: 23 | self.standup_dev_2_name_edit = standup["developers"][1]["dev_name"] 24 | self.standup_dev_2_email_edit = standup["developers"][1]["dev_email"] 25 | except IndexError: 26 | pass 27 | 28 | try: 29 | self.standup_dev_3_name_edit = standup["developers"][2]["dev_name"] 30 | self.standup_dev_3_email_edit = standup["developers"][2]["dev_email"] 31 | except IndexError: 32 | pass 33 | 34 | try: 35 | self.standup_dev_4_name_edit = standup["developers"][3]["dev_name"] 36 | self.standup_dev_4_email_edit = standup["developers"][3]["dev_email"] 37 | except IndexError: 38 | pass 39 | 40 | 41 | send_email(self.last_actor, 42 | """An existing Standup has been found. Please edit here: 43 | {{forms.standup_schedule_edit}} 44 | """, subject="Edit Existing Standup") -------------------------------------------------------------------------------- /standup/scripts/standup_new_standup_schedule.py: -------------------------------------------------------------------------------- 1 | class standup_new_standup_schedule(NebriOS): 2 | listens_to = ['standup_schedule_new'] 3 | 4 | def check(self): 5 | if shared.standup_roster: 6 | for standup in shared.standup_roster: 7 | if standup["project_name"] == self.standup_project_name: 8 | new_child.standup_project_name_lookup = self.standup_project_name 9 | new_child.standup_creation_attempted = True 10 | return False 11 | return True 12 | else: 13 | shared.standup_roster = [] 14 | return True 15 | 16 | 17 | def action(self): 18 | 19 | ## Forms should be able to handle Dictionaries as input 20 | dev_list = [] 21 | if self.standup_dev_1_name and self.standup_dev_1_email: 22 | dev_list.append({"dev_name":self.standup_dev_1_name, "dev_email":self.standup_dev_1_email}) 23 | if self.standup_dev_2_name and self.standup_dev_2_email: 24 | dev_list.append({"dev_name":self.standup_dev_2_name, "dev_email":self.standup_dev_2_email}) 25 | if self.standup_dev_3_name and self.standup_dev_3_email: 26 | dev_list.append({"dev_name":self.standup_dev_3_name, "dev_email":self.standup_dev_3_email}) 27 | if self.standup_dev_4_name and self.standup_dev_4_email: 28 | dev_list.append({"dev_name":self.standup_dev_4_name, "dev_email":self.standup_dev_4_email}) 29 | 30 | ## Convert DateTime value from Form to simply Time? Date is irrelevant here. 31 | 32 | shared.standup_roster.append({"project_name":self.standup_project_name, "standup_time":self.standup_time, "project_manager":self.standup_project_manager, "project_manager_email":self.standup_project_manager_email, "status":"idle", "developers":dev_list}) 33 | 34 | self.status = "Standup Successfully Scheduled" -------------------------------------------------------------------------------- /standup/scripts/standup_reset_roster.py: -------------------------------------------------------------------------------- 1 | class standup_reset_roster(NebriOS): 2 | listens_to = ['standup_reset_roster'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | 9 | for standup in shared.standup_roster: 10 | 11 | if "last_run" in standup: 12 | 13 | standup["last_run"] = parse_datetime(standup["last_run"]) - timedelta(days=1) 14 | standup["status"] = "idle" 15 | standup["standup_time"] = datetime.now() + timedelta(minutes=30) -------------------------------------------------------------------------------- /standup/scripts/standup_schedule_edit.py: -------------------------------------------------------------------------------- 1 | class standup_schedule_edit(NebriOS): 2 | listens_to = ['standup_schedule_edit'] 3 | 4 | def check(self): 5 | return True 6 | 7 | def action(self): 8 | if shared.standup_roster: 9 | for standup in shared.standup_roster: 10 | if standup["project_name"] == self.standup_project_name_lookup: 11 | standup["project_name"] = self.standup_project_name_edit 12 | standup["standup_time"] = self.standup_time_edit 13 | standup["project_manager"] = self.standup_project_manager_edit 14 | standup["project_manager_email"] = self.standup_project_manager_email_edit 15 | 16 | del standup["developers"] 17 | 18 | # Again, this mess is because Forms don't handle Dictionaries 19 | 20 | dev_list = [] 21 | 22 | dev_list.append({"dev_name":self.standup_dev_1_name_edit, "dev_email":self.standup_dev_1_email_edit}) 23 | 24 | if self.standup_dev_2_name_edit and self.standup_dev_2_email_edit: 25 | dev_list.append({"dev_name":self.standup_dev_2_name_edit, "dev_email":self.standup_dev_2_email_edit}) 26 | 27 | if self.standup_dev_3_name_edit and self.standup_dev_3_email_edit: 28 | dev_list.append({"dev_name":self.standup_dev_3_name_edit, "dev_email":self.standup_dev_3_email_edit}) 29 | 30 | if self.standup_dev_4_name_edit and self.standup_dev_4_email_edit: 31 | dev_list.append({"dev_name":self.standup_dev_4_name_edit, "dev_email":self.standup_dev_4_email_edit}) 32 | 33 | standup["developers"] = dev_list 34 | 35 | self.status = "Standup Successfully Edited" -------------------------------------------------------------------------------- /stripe_connector: -------------------------------------------------------------------------------- 1 | import stripe 2 | import json 3 | 4 | 5 | class stripe_connector(NebriOS): 6 | listens_to = ['stripe_connect'] 7 | 8 | def check(self): 9 | return self.stripe_connect == True 10 | 11 | def action(self): 12 | # fill in secret key, or use a shared kvp 13 | SECRET_KEY = '' 14 | stripe.api_key = SECRET_KEY 15 | s = stripe.Charge.all(limit=1) 16 | # the API returns latest payments first 17 | arr = s['data'] 18 | s_data = "null" 19 | if len(arr) > 0: 20 | s_data = arr[0] 21 | 22 | s_data = json.dumps(s_data) 23 | 24 | self.latest_payment = s_data 25 | -------------------------------------------------------------------------------- /thermostat.py: -------------------------------------------------------------------------------- 1 | # pip install first 2 | import thermostat 3 | 4 | class thermostat(NebriOS): 5 | # this KVP is updated from another script 6 | # that checks the thermostat 7 | listens_to == ['shared.temperature'] 8 | 9 | # determine if the script should run 10 | def check(self): 11 | return shared.temperature > 79 12 | 13 | # if the check is true, action() runs 14 | def action(self): 15 | send_email("manager@example.com", "Temperature Adjusted") 16 | 17 | # install and call any Python package 18 | thermostat.set(75) 19 | -------------------------------------------------------------------------------- /trello/trello_log_nonauthor_changes.py: -------------------------------------------------------------------------------- 1 | import httplib2 2 | import json 3 | import logging 4 | from urllib import urlencode 5 | 6 | 7 | logging.basicConfig(filename='trello-change-check.log', level=logging.DEBUG) 8 | 9 | # Get key here: https://trello.com/app-key 10 | # Add shared KVP 'TRELLO_KEY' with a value of the key 11 | # Get token here, replacing API_KEY with the key you got above 12 | # https://trello.com/1/authorize?key=API_KEY&name=Nebri+Scriptrunner&expiration=never&response_type=token 13 | # Add shared KVP trello_tocken and trello_key with the appropriate values 14 | 15 | class trello_log_nonauthor_changes(NebriOS): 16 | """ 17 | This class finds cards across all boards your API has access to 18 | and logs them to Nebri. Other rules reacto to it to send notifcations 19 | and such. 20 | """ 21 | listens_to = ['trello_check_cards'] 22 | # this should be a drip 23 | 24 | def check(self): 25 | return self.trello_check_cards == True 26 | 27 | def build_url(self, path, query={}): 28 | url = 'https://api.trello.com/1' 29 | if path[0:1] != '/': 30 | url += '/' 31 | url += path 32 | url += '?' 33 | url += "key=" + shared.TRELLO_KEY 34 | url += "&token=" + shared.TRELLO_TOKEN 35 | 36 | if len(query) > 0: 37 | url += '&' + urlencode(query) 38 | 39 | #logging.debug('[Url] >> %s' % (url,)) 40 | return url 41 | 42 | def fetch_json(self, uri_path, query_params={}): 43 | url = self.build_url(uri_path, query_params) 44 | 45 | client = httplib2.Http() 46 | response, content = client.request( 47 | url, 'GET', headers={'Accept': 'application/json'} 48 | ) 49 | 50 | # error checking 51 | if response.status == 401: 52 | raise Exception("Resource unavailable: %s (HTTP status: %s)" % ( 53 | url, response.status), response.status) 54 | if response.status != 200: 55 | raise Exception("Resource unavailable: %s (HTTP status: %s)" % ( 56 | url, response.status), response.status) 57 | 58 | json_loaded_content = json.loads(content) 59 | 60 | return json_loaded_content 61 | 62 | def action(self): 63 | self.trello_check_cards = False 64 | boards = self.fetch_json('/members/me/boards') 65 | #logging.debug('[Boards] >> %s' % (boards,)) 66 | 67 | actions = [] 68 | for board in boards: 69 | change_actions = self.fetch_json( 70 | '/boards/' + board['id'] + '/actions', 71 | query_params={'filter': 'updateCard:name,updateCard:desc'} 72 | ) 73 | create_actions = self.fetch_json( 74 | '/boards/'+board['id']+'/actions', 75 | query_params={'filter':'createCard'} 76 | ) 77 | #logging.debug('[Change actions]: %s' % (change_actions,)) 78 | #logging.debug('[Create actions]: %s' % (create_actions,)) 79 | for action in change_actions: 80 | actor_id = action['idMemberCreator'] 81 | for caction in create_actions: 82 | if action['data']['card']['id'] == caction['data']['card']['id']: 83 | creator_id = caction['idMemberCreator'] 84 | if actor_id != creator_id: 85 | # We've found a change by someone other than the author! 86 | if shared.trello_notified_actions == None: 87 | shared.trello_notified_actions = [] 88 | 89 | if action['id'] not in shared.trello_notified_actions: 90 | shared.trello_notified_actions.append(action['id']) 91 | ticket_name = action['data']['card']['name'] 92 | if len(ticket_name) > 40: 93 | ticket_name = ticket_name[:37] + '...' 94 | 95 | if action.get('data') and action.get('data').get('card') and action.get('data').get('card').get('shortLink'): 96 | card_url = 'https://trello.com/c/%s/' % (action['data']['card']['shortLink'],) 97 | #logging.debug('[Affected card]: %s' % (card_url,)) 98 | self.trello_card_changeset = { 99 | 'author': caction['memberCreator']['username'], 100 | 'changed_by': action['memberCreator']['username'], 101 | 'changeId': action['id'], 102 | 'old': action['data']['old'], 103 | 'board': {'id':board['id'],'name': board['name'] }, 104 | 'date': action['date'], 105 | 'card_name': ticket_name, 106 | 'url': card_url 107 | } 108 | break 109 | 110 | 111 | -------------------------------------------------------------------------------- /trello/trello_nonauthor_change_alert.py: -------------------------------------------------------------------------------- 1 | class trello_nonauthor_change_alert(NebriOS): 2 | listens_to = ['trello_card_changeset'] 3 | 4 | def check(self): 5 | #logging.debug('[Checking changeset] >> %s' % (self.trello_card_changeset,)) 6 | return self.trello_card_changeset != None 7 | 8 | def action(self): 9 | cset = self.trello_card_changeset 10 | self.trello_card_changeset = None 11 | 12 | if cset['author'] == 'trello-user-name-here': 13 | #logging.debug('[Sending email] >> %s' % (cset,)) 14 | send_email ("me@example.com",""" 15 | A trello card created by you was modified by '%s' on %s. The %s was changed. 16 | Go to the board: %s 17 | """ % (cset['changed_by'], cset['date'], cset['old'].keys()[0], cset['url'], cset['url'])) 18 | -------------------------------------------------------------------------------- /twilio_sms.py: -------------------------------------------------------------------------------- 1 | # pip install twilio 2 | from twilio.rest import TwilioRestClient 3 | 4 | ACCOUNT_SID = "" # SID from Twilio account 5 | AUTH_TOKEN = "" # Token from Twilio account 6 | 7 | class send_sms_on_change(NebriOS): 8 | listens_to = ['sms_to', 'sms_message'] 9 | 10 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 11 | 12 | def check(self): 13 | return self.sms_to is not None and self.sms_message is not None 14 | 15 | def action(self): 16 | self.client.messages.create( 17 | to="+%s" % self.sms_to, 18 | from_="+197xxxxxxxx", # your Twilio number, required 19 | body=str(self.sms_message), 20 | ) 21 | 22 | 23 | -------------------------------------------------------------------------------- /twitter_connect.py: -------------------------------------------------------------------------------- 1 | import twitter # from python-twitter package 2 | 3 | class twitter_connect(NebriOS): 4 | listens_to = ['twitter_connect'] 5 | 6 | def check(self): 7 | return self.twitter_connect == True and self.twitter_screen_name != '' 8 | 9 | def action(self): 10 | # pull from shared kvps 11 | api = twitter.Api( 12 | consumer_key=consumer_key, 13 | consumer_secret=consumer_secret, 14 | access_token_key=access_key, 15 | access_token_secret=access_secret 16 | ) 17 | 18 | twitter_user = api.GetUser(screen_name=self.twitter_screen_name) 19 | self.twitter_user_dict = twitter_user.AsDict() 20 | -------------------------------------------------------------------------------- /url_monitoring/add-url-monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | -------------------------------------------------------------------------------- /url_monitoring/add_url_monitor.py: -------------------------------------------------------------------------------- 1 | class add_url_monitor(NebriOS): 2 | listens_to = ['add_url_monitor'] 3 | 4 | def check(self): 5 | if self.add_url_monitor == True: 6 | if not self.url in shared.monitored_urls: 7 | return True 8 | 9 | def action(self): 10 | self.add_url_monitor = False 11 | 12 | self.add_url_monitor_status = "Ran at %s" % datetime.now() 13 | 14 | shared.monitored_urls.append(self.url) 15 | 16 | self.show_monitored_urls = True 17 | -------------------------------------------------------------------------------- /url_monitoring/monitored-urls.html: -------------------------------------------------------------------------------- 1 | 2 | 31 | 43 | -------------------------------------------------------------------------------- /url_monitoring/remove_url_monitor.py: -------------------------------------------------------------------------------- 1 | class remove_url_monitor(NebriOS): 2 | listens_to = ['remove_url_monitor'] 3 | 4 | def check(self): 5 | if self.remove_url_monitor == True: 6 | if self.url in shared.monitored_urls: 7 | return True 8 | 9 | def action(self): 10 | self.remove_url_monitor = False 11 | 12 | self.remove_url_monitor_status = "Ran at %s" % datetime.now() 13 | 14 | shared.monitored_urls.remove(self.url) 15 | 16 | self.show_monitored_urls = True -------------------------------------------------------------------------------- /url_monitoring/show_monitored_urls.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | class show_monitored_urls(NebriOS): 4 | listens_to = ['show_monitored_urls'] 5 | 6 | def check(self): 7 | return self.show_monitored_urls == True 8 | 9 | def action(self): 10 | self.show_monitored_urls = False 11 | 12 | self.show_monitored_urls_status = "Ran at %s" % datetime.now() 13 | 14 | self.monitored_urls = copy.deepcopy(shared.monitored_urls) 15 | 16 | load_card("monitored-urls") 17 | -------------------------------------------------------------------------------- /url_monitoring/url-entry.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | 24 | -------------------------------------------------------------------------------- /url_monitoring/url_contact_map.py: -------------------------------------------------------------------------------- 1 | class url_contact_map(NebriOS): 2 | listens_to = ['broken_urls'] 3 | 4 | def check(self): 5 | if self.broken_urls != []: 6 | return True 7 | 8 | def action(self): 9 | #to_contact = shared.url_watch_map[self.url_down] 10 | # program if from a shared kvp later 11 | 12 | self.contactor = [] 13 | 14 | for url in self.broken_urls: 15 | contact_email = "" 16 | contact_cell = 17 | self.contactor.append({'priority':2, 'email': contact_email, 'cell': contact_cell, 'message': 'Your website is down! URL: %s' % url, 'subject': 'Site is down: %s' % url}) 18 | # priority 1 - cell/email - now 19 | # priority 2 - email now cell later 20 | # priority 3 - email now 21 | 22 | #send_email("nick@bixly.com", "This Url is broken: %s" % url, subject="Broken Url: %s" % url) 23 | -------------------------------------------------------------------------------- /wolfram_alpha.py: -------------------------------------------------------------------------------- 1 | """ Sends email alert when company revenue becomes large certain threshold. 2 | It uses Wolfram Alpha. 3 | """ 4 | import re 5 | import requests 6 | 7 | def get_company_revenue(company): 8 | """ Finds revenue of a company using Wolfram Alpha 9 | Returns revenue in billions of US dollars 10 | """ 11 | resp = requests.get("http://www.wolframalpha.com/input/?i={}+revenue".format(company)) 12 | reg_exp = re.compile( 13 | r"""/input/ 14 | \?i=(\d+(\.\d+)?) 15 | \+billion 16 | \+US 17 | \+dollars 18 | \+per 19 | \+year""", re.VERBOSE) 20 | match = re.search(reg_exp, resp.text) 21 | if match: 22 | return float(match.group(1)) 23 | else: 24 | raise ValueError("Can't extract revenue data") 25 | 26 | class wolframQuery(NebriOS): 27 | schedule = "0 0 * * *" # daily 28 | #schedule = "* * * * *" # each minute 29 | my_email = "email@example.com" 30 | company_name = "google" 31 | threshold_revenue = 300 #in billions 32 | 33 | def check(self): 34 | return get_company_revenue(self.company_name) >= self.threshold_revenue 35 | 36 | def action(self): 37 | self.example = "Ran" 38 | send_email (self.my_email, 39 | """ Revenue of company {} is now {} billions of US dollars 40 | """.format(self.company_name, get_company_revenue(self.company_name)) 41 | ) 42 | -------------------------------------------------------------------------------- /yahoo_finance_connector.py: -------------------------------------------------------------------------------- 1 | from yahoo_finance import Share 2 | 3 | 4 | class yahoo_finance_connector(NebriOS): 5 | listens_to = ['yahoo_finance_connect'] 6 | # set a drip at the frequency you like 7 | 8 | def check(self): 9 | return self.yahoo_finance_connect == True 10 | 11 | def action(self): 12 | self.yahoo_finance_connect = "RAN" 13 | code = 'GOOG' # stock symbol here 14 | 15 | s = Share(code) 16 | price = s.get_price() 17 | 18 | self.price = price 19 | --------------------------------------------------------------------------------