def _get_router_service_description(cls, url_scheme, base_url, root_url): """ Examines the given router to find the control URL, serial number, and UUID """ from xml.etree import ElementTree response = http_request(url_scheme, base_url, root_url) # Parse the returned XML and find the <URLBase> and <controlURL> elements xml = ElementTree.fromstring(response) serial_number = next((x.text for x in xml.findall( ".//{urn:schemas-upnp-org:device-1-0}serialNumber")), None) # The UUID field contains the text "uuid:" before the actual UUID value. This is removed # and just the actual UUID is returned. # Example: uuid:11111111-2222-3333-4444-555555555555 becomes 11111111-2222-3333-4444-555555555555 uuid = next( (x.text for x in xml.findall(".//{urn:schemas-upnp-org:device-1-0}UDN")), None) if uuid: uuid = uuid.split(":")[1] for svc in xml.findall(".//{urn:schemas-upnp-org:device-1-0}service"): svc_type = svc.find( ".//{urn:schemas-upnp-org:device-1-0}serviceType").text control_url = svc.find( ".//{urn:schemas-upnp-org:device-1-0}controlURL").text if SSDP._is_wanip_service(svc_type): return (serial_number, control_url, uuid, svc_type) return (None, None, None, None)
def delete_port_mapping(cls, router, protocol, public_port): """ Deletes a port mapping from a router """ log.add_debug("Deleting port mapping (%s, %s, %s)", (router, protocol, public_port)) url = '{}{}'.format(router.base_url, router.control_url) log.add_debug('Deleting port mapping (%s/%s) at url "%s"', (public_port, protocol, url)) headers = { 'Host': '{}:{}'.format(router.ip, router.port), 'Content-Type': 'text/xml; charset=utf-8', 'SOAPACTION': '{}#DeletePortMapping'.format(router.type) } data = UPnp._delete_port_mapping_template.format(public_port, protocol) log.add_debug('UPnP: Delete port mapping request: %s', data) response = http_request(router.url_scheme, router.base_url, router.control_url, request_type="POST", body=data, headers=headers) log.add_debug('UPnP: Delete port mapping response: %s', response.encode('utf-8'))
def _request_port_mapping(self, router, protocol, public_port, private_ip, private_port, mapping_description, lease_duration): """ Function that adds a port mapping to the router. If a port mapping already exists, it is updated with a lease period of 24 hours. """ from xml.etree import ElementTree url = '%s%s' % (router.base_url, router.control_url) log.add_debug( "UPnP: Adding port mapping (%s %s/%s, %s) at url '%s'", (private_ip, private_port, protocol, router.search_target, url)) headers = { "Host": router.base_url, "Content-Type": "text/xml; charset=utf-8", "SOAPACTION": '"%s#AddPortMapping"' % router.service_type } body = ( self.request_body % (router.service_type, public_port, protocol, private_port, private_ip, mapping_description, lease_duration)).encode('utf-8') log.add_debug("UPnP: Add port mapping request headers: %s", headers) log.add_debug("UPnP: Add port mapping request contents: %s", body) response = http_request(router.url_scheme, router.base_url, router.control_url, request_type="POST", body=body, headers=headers) xml = ElementTree.fromstring(response) if xml.find( ".//{http://schemas.xmlsoap.org/soap/envelope/}Body") is None: raise Exception( _("Invalid response: %s") % response.encode('utf-8')) log.add_debug("UPnP: Add port mapping response: %s", response.encode('utf-8')) error_code = xml.findtext( ".//{urn:schemas-upnp-org:control-1-0}errorCode") error_description = xml.findtext( ".//{urn:schemas-upnp-org:control-1-0}errorDescription") if error_code or error_description: raise Exception( _("Error code %(code)s: %(description)s") % { "code": error_code, "description": error_description })
def lastfm(self, user): """ Function to get the last song played via Last.fm API """ try: user, apikey = user.split(';') except ValueError: log.add_important_error( _("Last.fm: Please provide both your Last.fm username and API key" )) return None try: response = http_request( "https", "ws.audioscrobbler.com", "/2.0/?method=user.getrecenttracks&user="******"&api_key=" + apikey + "&limit=1&format=json", headers={"User-Agent": self.config.application_name}) except Exception as error: log.add_important_error( _("Last.fm: Could not connect to Audioscrobbler: %(error)s"), {"error": error}) return None try: json_api = json.loads(response) lastplayed = json_api["recenttracks"]["track"] try: # In most cases, a list containing a single track dictionary is sent lastplayed = lastplayed[0] except KeyError: # On rare occasions, the track dictionary is not wrapped in a list pass self.title["artist"] = lastplayed["artist"]["#text"] self.title["title"] = lastplayed["name"] self.title["album"] = lastplayed["album"]["#text"] self.title["nowplaying"] = "%s: %s - %s - %s" % ( _("Last played"), self.title["artist"], self.title["album"], self.title["title"]) except Exception: log.add_important_error( _("Last.fm: Could not get recent track from Audioscrobbler: %(error)s" ), {"error": response}) return None return True
def add_port_mapping(cls, router, protocol, public_port, private_ip, private_port, mapping_description, lease_duration): """ Adds a port mapping to a router """ from xml.etree import ElementTree log.add_debug( "Adding port mapping (%s, %s, %s, %s, %s)", (router.uuid, protocol, public_port, private_ip, private_port)) url = '{}{}'.format(router.base_url, router.control_url) log.add_debug('Adding port mapping (%s %s/%s) at url "%s"', (private_ip, private_port, protocol, url)) headers = { 'Host': '{}:{}'.format(router.ip, router.port), 'Content-Type': 'text/xml; charset=utf-8', 'SOAPACTION': '"{}#AddPortMapping"'.format(router.svc_type) } data = UPnp._add_port_mapping_template.format(public_port, protocol, private_port, private_ip, mapping_description, lease_duration) log.add_debug('UPnP: Add port mapping request headers: %s', headers) log.add_debug('UPnP: Add port mapping request contents: %s', data) response = http_request(router.url_scheme, router.base_url, router.control_url, request_type="POST", body=data, headers=headers) log.add_debug('UPnP: Add port mapping response: %s', response.encode('utf-8')) xml = ElementTree.fromstring(response) error_code = next((x.text for x in xml.findall( ".//{urn:schemas-upnp-org:control-1-0}errorCode")), None) error_description = next((x.text for x in xml.findall( ".//{urn:schemas-upnp-org:control-1-0}errorDescription")), None) if error_code or error_description: raise Exception('Error code %(code)s: %(description)s' % { 'code': error_code, 'description': error_description })
def listenbrainz(self, username): """ Function to get the currently playing song via ListenBrainz API """ if not username: log.add_important_error( _("ListenBrainz: Please provide your ListenBrainz username")) return None try: response = http_request( 'https', 'api.listenbrainz.org', '/1/user/{}/playing-now'.format(username), headers={'User-Agent': self.config.application_name}) except Exception as error: log.add_important_error( _("ListenBrainz: Could not connect to ListenBrainz: %(error)s" ), {'error': error}) return None try: json_api = json.loads(response)['payload'] if not json_api['playing_now']: log.add_important_error( _("ListenBrainz: You don\'t seem to be listening to anything right now" )) return None track = json_api['listens'][0]['track_metadata'] self.title['artist'] = track['artist_name'] self.title['title'] = track['track_name'] self.title['album'] = track['release_name'] self.title['nowplaying'] = '%s: %s - %s - %s' % ( _('Playing now'), self.title['artist'], self.title['album'], self.title['title']) return True except Exception: log.add_important_error( _("ListenBrainz: Could not get current track from ListenBrainz: %(error)s" ), {'error': str(response)}) return None
def list_port_mappings(cls, router): """ Lists the port mappings for a router """ log.add_debug('Listing existing port mappings...') headers = { 'Host': '{}:{}'.format(router.ip, router.port), 'Content-Type': 'text/xml; charset=utf-8', 'SOAPACTION': '"{}#GetGenericPortMappingEntry"'.format(router.svc_type) } index = -1 portmap_found = True portmaps = [] while portmap_found: index += 1 data = UPnp._list_port_mappings_template.format(index) log.add_debug('UPnP: List port mappings request headers: %s', headers) log.add_debug('UPnP: List port mappings request contents: %s', data) response = http_request(router.url_scheme, router.base_url, router.control_url, request_type="POST", body=data, headers=headers) log.add_debug('UPnP: List port mappings response: %s', response.encode('utf-8')) portmap = PortMapping.parse_port_map_xml(response, router.svc_type) if not portmap: portmap_found = False else: portmaps.append(portmap) log.add_debug('Existing port mappings: %s', portmaps) return portmaps
def incoming_public_chat_notification(self, room, user, line): line = line.lower().strip() if not line.startswith(self.plugin_command) or " " not in line: return subreddit = line.split(" ")[1].strip("/") if not self.responder.ok_to_respond(room, user, subreddit): return try: response = http_request( 'https', 'www.reddit.com', '/r/' + subreddit + '/.json', headers={"User-Agent": self.config.application_name}) except Exception as error: self.log("Could not connect to Reddit: %(error)s", {"error": error}) return try: response = json.loads(response) for post in islice(response['data']['children'], self.settings['reddit_links']): post_data = post['data'] self.send_public( room, "/me {}: {}".format(post_data['title'], post_data['url'])) except Exception as error: self.log("Failed to parse response from Reddit: %(error)s", {"error": error}) return self.responder.responded()
def get_router_control_url(url_scheme, base_url, root_url): service_type = None control_url = None try: from xml.etree import ElementTree response = http_request(url_scheme, base_url, root_url, timeout=2) log.add_debug( "UPnP: Device description response from %s://%s%s: %s", (url_scheme, base_url, root_url, response.encode('utf-8'))) xml = ElementTree.fromstring(response) for service in xml.findall( ".//{urn:schemas-upnp-org:device-1-0}service"): found_service_type = service.find( ".//{urn:schemas-upnp-org:device-1-0}serviceType").text if found_service_type in ( "urn:schemas-upnp-org:service:WANIPConnection:1", "urn:schemas-upnp-org:service:WANPPPConnection:1", "urn:schemas-upnp-org:service:WANIPConnection:2"): # We found a router with UPnP enabled service_type = found_service_type control_url = service.find( ".//{urn:schemas-upnp-org:device-1-0}controlURL").text break except Exception as error: # Invalid response log.add_debug( "UPnP: Invalid device description response from %s://%s%s: %s", (url_scheme, base_url, root_url, error)) return service_type, control_url
def parse_response(self, video_id): if not self.settings['api_key']: self.log('No API key specified') return None try: response = http_request( 'https', 'www.googleapis.com', '/youtube/v3/videos?part=snippet,statistics,contentDetails&id={}&key={}' .format(video_id, self.settings['api_key']), headers={'User-Agent': self.config.application_name}) except Exception as error: self.log('Failed to connect to www.googleapis.com: %s', error) return None try: data = json.loads(response) except Exception as error: self.log('Failed to parse response from www.googleapis.com: %s', str(error)) return None if 'error' in data: error_message = data['error'].get('message', False) if not error_message: # This should not occur error_message = str(data['error']) self.log(error_message) return None total_results = data.get('pageInfo', {}).get('totalResults', False) if not total_results: if isinstance(total_results, int): # Video removed / invalid id self.log('Video unavailable') elif isinstance(total_results, bool): # This should not occur self.log('Youtube API appears to be broken') return None try: data = data['items'][0] title = data['snippet']['title'] description = data['snippet']['description'] channel = data['snippet']['channelTitle'] live = data['snippet']['liveBroadcastContent'] duration = data['contentDetails']['duration'] quality = data['contentDetails']['definition'].upper() views = data['statistics'].get('viewCount', 'RESTRICTED') likes = data['statistics'].get('likeCount', 'LIKES') except KeyError: # This should not occur self.log('An error occurred while parsing id "%s"', video_id) return None if likes != 'LIKES': likes = humanize(int(likes)) if views != 'RESTRICTED': views = humanize(int(views)) if live in ('live', 'upcoming'): duration = live.upper() else: duration = self.get_duration(duration) return { '%title%': title, '%description%': description, '%duration%': duration, '%quality%': quality, '%channel%': channel, '%views%': views, '%likes%': likes }