class PushBulletWSClient(WebSocketBaseClient): def init(self, interface): """ Initializes the PB WS Client""" self.logger = logging.getLogger('PAI').getChild(__name__) self.pb = Pushbullet(cfg.PUSHBULLET_KEY, cfg.PUSHBULLET_SECRET) self.manager = WebSocketManager() self.alarm = None self.interface = interface def stop(self): self.terminate() self.manager.stop() def set_alarm(self, alarm): """ Sets the paradox alarm object """ self.alarm = alarm def handshake_ok(self): """ Callback trigger when connection succeeded""" self.logger.info("Handshake OK") self.manager.add(self) self.manager.start() for chat in self.pb.chats: self.logger.debug("Associated contacts: {}".format(chat)) # Receiving pending messages self.received_message(json.dumps({"type": "tickle", "subtype": "push"})) self.send_message("Active") def received_message(self, message): """ Handle Pushbullet message. It should be a command """ self.logger.debug("Received Message {}".format(message)) try: message = json.loads(str(message)) except: self.logger.exception("Unable to parse message") return if self.alarm is None: return if message['type'] == 'tickle' and message['subtype'] == 'push': now = time.time() pushes = self.pb.get_pushes(modified_after=int(now) - 20, limit=1, filter_inactive=True) self.logger.debug("got pushes {}".format(pushes)) for p in pushes: self.pb.dismiss_push(p.get("iden")) self.pb.delete_push(p.get("iden")) if p.get('direction') == 'outgoing' or p.get('dismissed'): continue if p.get('sender_email_normalized') in cfg.PUSHBULLET_CONTACTS: ret = self.interface.send_command(p.get('body')) if ret: self.logger.info("From {} ACCEPTED: {}".format(p.get('sender_email_normalized'), p.get('body'))) else: self.logger.warning("From {} UNKNOWN: {}".format(p.get('sender_email_normalized'), p.get('body'))) else: self.logger.warning("Command from INVALID SENDER {}: {}".format(p.get('sender_email_normalized'), p.get('body'))) def unhandled_error(self, error): self.logger.error("{}".format(error)) try: self.terminate() except Exception: self.logger.exception("Closing Pushbullet WS") self.close() def send_message(self, msg, dstchat=None): if dstchat is None: dstchat = self.pb.chats if not isinstance(dstchat, list): dstchat = [dstchat] for chat in dstchat: if chat.email in cfg.PUSHBULLET_CONTACTS: try: self.pb.push_note("paradox", msg, chat=chat) except Exception: self.logger.exception("Sending message") time.sleep(5) def notify(self, source, message, level): try: if level.value >= EventLevel.WARN.value: self.send_message("{}".format(message)) except Exception: logging.exception("Pushbullet notify")
class PushBulletWSClient(WebSocketBaseClient): def init(self): """ Initializes the PB WS Client""" self.pb = Pushbullet(cfg.PUSHBULLET_KEY, cfg.PUSHBULLET_SECRET) self.manager = WebSocketManager() self.alarm = None def set_alarm(self, alarm): """ Sets the paradox alarm object """ self.alarm = alarm def handshake_ok(self): """ Callback trigger when connection succeeded""" logger.info("Handshake OK") self.manager.add(self) for chat in self.pb.chats: logger.debug("Associated contacts: {}".format(chat)) # Receiving pending messages self.received_message(json.dumps({ "type": "tickle", "subtype": "push" })) self.send_message("Active") def handle_message(self, message): """ Handle Pushbullet message. It should be a command """ logger.debug("Received Message {}".format(message)) try: message = json.loads(str(message)) except: logger.exception("Unable to parse message") return if self.alarm == None: return if message['type'] == 'tickle' and msg['subtype'] == 'push': now = time.time() pushes = self.pb.get_pushes(modified_after=int(now) - 10, limit=1, filter_inactive=True) for p in pushes: self.pb.dismiss_push(p.get("iden")) self.pb.delete_push(p.get("iden")) if p.get('direction') == 'outgoing' or p.get('dismissed'): continue if p.get('sender_email_normalized') in PUSHBULLET_CONTACTS: ret = self.send_command(p.get('body')) if ret: logger.info("From {} ACCEPTED: {}".format( p.get('sender_email_normalized'), p.get('body'))) else: logger.warning("From {} UNKNOWN: {}".format( p.get('sender_email_normalized'), p.get('body'))) else: logger.warning("Command from INVALID SENDER {}: {}".format( p.get('sender_email_normalized'), p.get('body'))) def unhandled_error(self, error): logger.error("{}".format(error)) try: self.terminate() except: logger.exception("Closing Pushbullet WS") self.close() def send_message(self, msg, dstchat=None): for chat in self.pb.chats: if chat.email in PUSHBULLET_CONTACTS: try: self.pb.push_note("paradox", msg, chat=chat) except: logger.exception("Sending message") time.sleep(5) def send_command(self, message): """Handle message received from the MQTT broker""" """Format TYPE LABEL COMMAND """ tokens = message.split(" ") if len(tokens) != 3: logger.warning("Message format is invalid") return if self.alarm == None: logger.error("No alarm registered") return element_type = tokens[0].lower() element = tokens[1] command = self.normalize_payload(tokens[2]) # Process a Zone Command if element_type == 'zone': if command not in ['bypass', 'clear_bypass']: logger.error("Invalid command for Zone {}".format(command)) return if not self.alarm.control_zone(element, command): logger.warning("Zone command refused: {}={}".format( element, command)) # Process a Partition Command elif element_type == 'partition': if command not in ['arm', 'disarm', 'arm_stay', 'arm_sleep']: logger.error( "Invalid command for Partition {}".format(command)) return if not self.alarm.control_partition(element, command): logger.warning("Partition command refused: {}={}".format( element, command)) # Process an Output Command elif element_type == 'output': if command not in ['on', 'off', 'pulse']: logger.error("Invalid command for Output {}".format(command)) return if not self.alarm.control_output(element, command): logger.warning("Output command refused: {}={}".format( element, command)) else: logger.error("Invalid control property {}".format(element)) def normalize_payload(self, message): message = message.strip().lower() if message in ['true', 'on', '1', 'enable']: return 'on' elif message in ['false', 'off', '0', 'disable']: return 'off' elif message in [ 'pulse', 'arm', 'disarm', 'arm_stay', 'arm_sleep', 'bypass', 'clear_bypass' ]: return message return None def notify(self, source, message, level): if level < logging.INFO: return try: self.send_message("{}".format(message)) except: logging.exception("Pushbullet notify") def event(self, raw): """Handle Live Event""" return def change(self, element, label, property, value): """Handle Property Change""" #logger.debug("Property Change: element={}, label={}, property={}, value={}".format( # element, # label, # property, # value)) return
class PushbulletManager(): """Manager interface to and from PushBullet. Note: Requires that the environment variable PUSHBULLET_API_TOKEN is set. """ REMINDER_KEYWORD = r"remi(nder)?\s+" last_mod_time = 0.0 def __init__(self): """Create PushBullet listener.""" self.logger = logging.getLogger(__name__) self.logger.info("Starting PushbulletManager...") self.pb = Pushbullet(os.environ.get("PUSHBULLET_API_TOKEN")) push_list = self.pb.get_pushes(limit=1) if push_list: self.last_mod_time = push_list[0]["modified"] self.pb_listener = Listener(account=self.pb, on_push=self.pb_on_push, on_error=self.pb_on_error, http_proxy_host=HTTP_PROXY_HOST, http_proxy_port=HTTP_PROXY_PORT) self.rmc = ReminderMessageConsumer() self.rmc.start() def run(self): """Listen for messages on PushBullet websocket.""" try: self.logger.info('Waiting for messages. To exit press CTRL+C') self.pb_listener.run_forever() except KeyboardInterrupt: self.rmc.stop() self.rmc.join() raise finally: self.pb_listener.close() def pb_on_error(self, websocket, exception): """Error handler for the PushBullet listener. Re-raise any exception that occurs, as otherwise it will be swallowed up by the PushBullet Listener. """ # If a KeyboardInterrupt is raised during # self.pb_listener.run_forever(), this method is invoked, which is the # only way we'll know about it. raise exception def pb_on_push(self, data): """Push handler for the PushBullet listener""" try: self.logger.debug("Received tickle: %s", str(data)) push_list = self.pb.get_pushes(limit=1, modified_after=self.last_mod_time) if not push_list: self.logger.debug("Ignoring tickle") return latest = push_list[0] self.last_mod_time = latest["modified"] self.logger.info("Latest Push: %s", str(latest)) self.process_push(latest) except Exception: self.logger.exception("Error processing push message") def process_push(self, push): """Process PushBullet JSON message. Passes the message to the ReminderManager if appropriate """ # Pushbullet can also send Links and Images, which don't have a "body" if "body" not in push: self.logger.info("Ignoring non-note push: id=%s type=%s", push["iden"], push["type"]) return if push["dismissed"]: self.logger.debug("Ignoring dismissed push: id=%s, body=%s", push["iden"], push["body"]) return # We require the reminder keyword at beginning of message, to avoid # an infinite loop of pushbullets. if not bool(re.match(self.REMINDER_KEYWORD, push["body"], re.I)): self.logger.info("Ignoring non-reminder push: id=%s, body=%s", push["iden"], push["body"]) return self.logger.info('Received push: id=%s, body=%s', push["iden"], push["body"]) # Strip off reminder keyword first body = re.sub(r'^\S+\s+', '', push["body"]) request = {"body": body, "source": "pushbullet"} self.logger.info("Sending request to reminder RPC service") try: reminder_rpc = ReminderRpcClient() response = reminder_rpc.call(request) self.logger.info("Reminder RPC response: %s", str(response)) except ResponseTimeout as rto: self.logger.exception(str(rto)) response = { "error": { "code": 0, "text": "Unable to connect to server" } } if "error" in response: self.logger.error("Reminder RPC returned error code '%d' (%s)", response["error"]["code"], response["error"]["text"]) self.pb.push_note("Error Setting Reminder", response["error"]["text"]) else: self.logger.info("Received response: %s", str(response)) self.logger.info("Dismissing push: id=%s", push["iden"]) self.pb.dismiss_push(push["iden"]) self.logger.info("Sending response: %s", response["data"]["text"]) self.pb.push_note("Got it!", response["data"]["text"])
def lambda_handler(*args): timetable = { '1': '08:40', '2': '09:40', '3': '10:40', '4': '11:40', '5': '12:40', '6': '13:30', '7': '14:30', '8': '15:30', '9': '16:30', '10': '17:40', '11': '18:40', '12': '19:40', '13': '20:40' } school_ip = { 'futakotamagawa': '125.206.202.147', 'seijo': '153.150.125.233', 'shakujii': '221.186.136.107', 'akitsu': '125.206.199.163', 'tsunashima': '125.206.214.67' } IP = school_ip[os.environ['KOYAMA_LOC']] LOGIN_URL = 'http://{}/scripts/mtr0010.asp'.format(IP) CALENDAR_URL = 'http://{}/scripts/mtr1010.asp'.format(IP) browser = mechanize.Browser() browser.open(LOGIN_URL) browser.form = list(browser.forms())[0] un_control = browser.form.find_control('mt0010uid') un_control.value = os.environ['KOYAMA_ID'] password_control = browser.form.find_control('mt0010pwd') password_control.value = os.environ['KOYAMA_PW'] browser.submit() browser.open(CALENDAR_URL) all_available = {} all_reserved = {} for i in xrange(0, int(os.environ['MAX_WEEKS'])): soup = BeautifulSoup( browser.response().read(), 'html.parser', from_encoding='shift-jis') # doesn't work with lxml/xml available = soup.find_all('input', src='/images/ko2_kusya.gif') print('week {}: {} available'.format(str(i), str(len(available)))) for date in available: period = date.attrs['name'][1:] date_string = date.parent.parent.contents[1].text try: all_available[date_string].append(timetable[period]) except KeyError: all_available[date_string] = [timetable[period]] try: browser.form = browser.forms()[0] browser.submit('next') except: break print(sorted(all_available)) message_text = u'\n'.join([ u'{}: {}'.format(date, ', '.join(times)) for (date, times) in sorted(all_available.iteritems()) ]) if os.environ['KOYAMA_PUSH_MODE'] in ('line', 'both'): if all_available: message_text += u'\n\n' + CALENDAR_URL url = 'https://api.line.me/v2/bot/message/push' headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer {{{}}}'.format( os.environ['LINE_CHANNEL_TOKEN']) # {{ → '{' } payload = { 'to': os.environ['LINE_MY_ID'], 'messages': [{ 'type': 'text', 'text': message_text }] } r = requests.post(url, headers=headers, data=json.dumps(payload)) print(r) else: print('None available.') if os.environ['KOYAMA_PUSH_MODE'] in ('pushbullet', 'both'): pb = Pushbullet(os.environ['PUSHBULLET_TOKEN']) if all_available: pushes = pb.get_pushes() try: most_recent = [ push for push in pushes if push['sender_name'] == 'Koyama Alert' ][0] if most_recent['body'] != message_text: pb.dismiss_push(most_recent['iden']) push = push_to_pushbullet(pb, message_text) except IndexError: push = push_to_pushbullet(pb, message_text) print(push) else: undismissed = [ push['iden'] for push in pb.get_pushes() if push['sender_name'] == 'Koyama Alert' and not push['dismissed'] ] for iden in undismissed: pb.dismiss_push(iden) print('None available. Dismissed pushes.') return None