diff --git a/py3status/mpdstatus.py b/py3status/mpdstatus.py new file mode 100644 index 0000000..d4d029b --- /dev/null +++ b/py3status/mpdstatus.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python + +"""mpdstatus py3status module. + +mpdstatus is a MPD module for py3status. +It shows the currently playing song and can be used to pause, resume or stop +playpack. + +Copyright (C) 2013 Tablet Mode + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see [http://www.gnu.org/licenses/]. + +""" + +from time import time + +from mpd import MPDClient, CommandError + + +class MPDstatusException(Exception): + + """Custom mpdstatus exception.""" + + def __init__(self, exception): + """Initialisation.""" + self.exception = exception + + def __str__(self): + """Prepend message with 'mpdstatus: '.""" + return "mpdstatus: {exception}".format(exception=self.exception) + + +class Data: + + """Aquire data.""" + + def __init__(self, host, port, password): + """Initialise MPD client.""" + self.count = 0 + self.HOST = host + self.PORT = port + self.PW = password + self.client = MPDClient() + self._connect() + + def _crop_text(self, text, length=32): + """Crop string to specified length.""" + if len(text) > length: + text = "{}...".format(text[:length]) + + return text + + def _connect(self): + """Connect to MPD.""" + try: + self.client.connect(self.HOST, self.PORT) + if self.PW: + self.client.password(self.PW) + except CommandError as e: + if "incorrect password" in str(e).lower(): + raise MPDstatusException("incorrect password") + except ConnectionRefusedError: + # This is handled elsewhere by displaying the text `not connected` + # in the status bar. + pass + + def disconnect(self): + """Close connection to MPD cleanly.""" + try: + self.client.close() + self.client.disconnect() + except: + # If this happens, the client is most likely already disconnected + # anyway. + pass + + def reconnect(self): + """Try to reaquire MPD connection.""" + self.disconnect() + self._connect() + + def has_connection(self): + """Check if MPD is reachable.""" + try: + self.client.status() + except: + return False + else: + return True + + def previous(self): + """Jump to previous song.""" + self.client.previous() + + def next(self): + """Go to next song.""" + self.client.next() + + def pause(self): + """Pause playback.""" + self.client.pause() + + def wrap_tag(self, song, tag): + if (tag in song): + return song[tag] + else: + return "" + + def get_stats(self, length): + """Return artist, songtitle and playback state.""" + song = self._crop_text(self.client.currentsong()) + status = self.client.status() + artist = self._crop_text( + song['artist']) if 'artist' in song else "Unknown Artist" + + if (self.wrap_tag(song, 'genre') == "Classical"): + if (self.wrap_tag(song, 'comment') != ""): + data = "%s: %s - %s" % \ + (self.wrap_tag(song,'composer'), + self.wrap_tag(song, 'comment'), + self.wrap_tag(song, 'title')) + else: + data = "%s: %s" % \ + (self.wrap_tag(song,'composer'), + self.wrap_tag(song, 'title')) + else: + data = "%s: %s" % (artist, self.wrap_tag(song,('title'))) + + return (self._crop_text(data, length=length), status['state']) + + +class Py3status: + + """This is where all the py3status magic happens.""" + + cache_timeout = 0 + name = 'MPD:' + host = 'localhost' + port = 6600 + password = '' + length = 40 + + def __init__(self): + """Initialisation.""" + self.data = None + + def kill(self, json, i3status_config, event): + """Handle termination.""" + self.data.disconnect() + + def on_click(self, json, i3status_config, event): + """Handle mouse clicks.""" + # Middle click: Go to previous song + if event['button'] == 2: + self.data.previous() + # Left click: Pause playback + elif event['button'] == 1: + self.data.pause() + # Right click: Jump to next song + elif event['button'] == 3: + self.data.next() + + def mpdstatus(self, json, i3status_config): + """Return response for i3status bar.""" + response = {'full_text': '', 'name': 'mpdstatus'} + + # Initialise Data class only once + # TODO: parse settings in separate function for better error handling + if not self.data: + self.data = Data(self.host, self.port, self.password) + + connection = self.data.has_connection() + + if connection: + data, state = self.data.get_stats(self.length) + + if state == 'play': + response['color'] = i3status_config['color_good'] + elif state == 'pause': + response['color'] = i3status_config['color_degraded'] + + response['full_text'] = data + else: + self.data.reconnect() + response['color'] = i3status_config['color_bad'] + response['full_text'] = "%s not connected" % (self.name) + + response['cached_until'] = time() + self.cache_timeout + + return response + + +if __name__ == "__main__": + from time import sleep + x = Py3status() + while True: + print(x.mpdstatus([], {})) + sleep(5) diff --git a/py3status/weather_yahoo.py b/py3status/weather_yahoo.py new file mode 100644 index 0000000..17bd783 --- /dev/null +++ b/py3status/weather_yahoo.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +""" +Display current day + 3 days weather forecast as icons on your i3bar +Based on Yahoo! Weather. forecast, thanks guys ! + http://developer.yahoo.com/weather/ + +Find your city code using: + http://answers.yahoo.com/question/index?qid=20091216132708AAf7o0g + +The city_code in this example is for Paris, France => FRXX0076 +""" + +from time import time +import requests + + +class Py3status: + + # available configuration parameters + cache_timeout = 1800 + city_code = 'FRXX0076' + forecast_days = 5 + request_timeout = 10 + + def _get_forecast(self): + """ + Ask Yahoo! Weather. for a forecast + """ + q = requests.get( + 'http://query.yahooapis.com/v1/public/yql?q=' + + 'select item from weather.forecast ' + + 'where location="%s"&format=json' % self.city_code, + timeout=self.request_timeout + ) + + r = q.json() + status = q.status_code + forecasts = [] + + if status == 200: + forecasts = r['query']['results']['channel']['item']['forecast'] + # reset today + forecasts[0] = r['query']['results']['channel']['item']['condition'] + else: + raise Exception('got status {}'.format(status)) + + # return current today + forecast_days days forecast + return forecasts[:self.forecast_days + 1] + + def _get_icon(self, forecast): + """ + Return an unicode icon based on the forecast code and text + See: http://developer.yahoo.com/weather/#codes + """ + icons = ['☀', '☁', '☂', '☃', '?'] + code = int(forecast['code']) + text = forecast['text'].lower() + + # sun + if 'sun' in text or code in [31, 32, 33, 34, 36]: + code = 0 + + # cloud / early rain + elif 'cloud' in text or code in [ + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 44 + ]: + code = 1 + + # rain + elif 'rain' in text or code in [ + 0, 1, 2, 3, 4, 5, 6, 9, + 11, 12, + 37, 38, 39, + 40, 45, 47 + ]: + code = 2 + + # snow + elif 'snow' in text or code in [ + 7, 8, + 10, 13, 14, 15, 16, 17, 18, + 35, + 41, 42, 43, 46 + ]: + code = 3 + + # dunno + else: + code = -1 + + return icons[code] + + def weather_yahoo(self, i3s_output_list, i3s_config): + """ + This method gets executed by py3status + """ + response = { + 'cached_until': time() + self.cache_timeout, + 'full_text': '' + } + + forecasts = self._get_forecast() + + response['full_text'] += '{}° ({}°/{}°) '.format(forecasts[0]['temp'], + forecasts[1]['high'], + forecasts[1]['low']) + + curicon = self._get_icon(forecasts[0]) + if (curicon == '☀'): + response['color'] = '#FFE181' + elif (curicon == '☁'): + response['color'] = '#C1D6EA' + elif (curicon == '☂'): + response['color'] = "#C1D6EA" + elif (curicon == '☃'): + response['color'] = '#ffffff' + + for forecast in forecasts: + icon = self._get_icon(forecast) + response['full_text'] += '{} '.format(icon) + response['full_text'] = response['full_text'].strip() + + return response + +if __name__ == "__main__": + """ + Test this module by calling it directly. + """ + from time import sleep + x = Py3status() + while True: + print(x.weather_yahoo([], {})) + sleep(1)