From 218bfa2235ddb2f3881ad93cc069f8eda40553f1 Mon Sep 17 00:00:00 2001 From: Tom Watson Date: Wed, 6 Jul 2022 01:05:25 +0700 Subject: [PATCH 1/3] Updated contrib/publicip module and util/location Added another API endpoint, Added options to display country name, country code, city name and lat/long coordinates, attempt to handle failure to fetch info from API endpoints cleanly --- bumblebee_status/modules/contrib/publicip.py | 127 ++++++++++++++++++- bumblebee_status/util/location.py | 49 ++++++- 2 files changed, 163 insertions(+), 13 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index a74d708..5b061f4 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -1,22 +1,109 @@ -"""Displays public IP address +""" +Displays public IP address and, optionally, Country Name, Country Code & City Name +Maximum refresh interval should be 5 minutes to avoid free SLA breach from providers +Note: 1 request/5 minutes is 8640 requests/month +Provider information contained in core.location +Left mouse click forces immediate update + +Parameters (Default in brackets)_ +ip (True) Public IP address +country_name (False) Display name of country associated with the IP +country_code (False) Display country code of country associated with the IP +city_name (False) Display name of city associated with the IP +coordinates (False) Display name of city associated with the IP +all (False) Display all information associate with the IP + +Examples +By default only the public IP is shown +bumblebee-status -m publicip + +To also include the country name... +bumblebee-status -m publicip -p publicip.country_name=True + +To include all ava + """ import core.module import core.widget import core.decorators - +import core.input +import util.format import util.location class Module(core.module.Module): - @core.decorators.every(minutes=60) + @core.decorators.every(minutes=5) def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.public_ip)) - self.__ip = "" + core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__click_update) + + self.__ip = "" # Public IP address + self.__country_name = "" # Country name associated with public IP address + self.__country_code = "" # Country code associated with public IP address + self.__city_name = "" # City name associated with public IP address + self.__coordinates = "" # Coordinated assoicated with public IP address + + # Handle failure to get IP information + self.__ip_error = False + + # Process option paramaters + self.__show_ip = util.format.asbool( + self.parameter("ip", True) + ) + self.__show_country_name = util.format.asbool( + self.parameter("country_name", False) + ) + self.__show_country_code = util.format.asbool( + self.parameter("country_code", False) + ) + self.__show_city_name = util.format.asbool(self.parameter("city_name", False)) + self.__show_coordinates = util.format.asbool( + self.parameter("coordinates", False) + ) + self.__show_all = util.format.asbool(self.parameter("all", False)) + + def __click_update(self, event): + util.location.reset() def public_ip(self, widget): - return self.__ip or "n/a" + __output = "" + + if self.__ip: + if self.__show_ip or self.__show_all: + __output = self.__ip + self.__ip_error = False + else: + self.__ip_error = True + __output = "Error Getting IP" + + if not self.__ip_error: + if self.__show_country_name or self.__show_all: + if self.__country_name: + __output += " " + self.__country_name + else: + __output += " " + "?" + + if self.__show_country_code or self.__show_all: + if self.__country_code: + __output += " " + "(" + self.__country_code + ")" + else: + __output += " " + "(?)" + + if self.__show_city_name or self.__show_all: + if self.__city_name: + __output += " " + self.__city_name + else: + __output += " " + "?" + + if self.__show_coordinates or self.__show_all: + if self.__coordinates: + __output += " " + self.__coordinates + else: + __output += " " + "?" + + return __output def update(self): try: @@ -24,5 +111,33 @@ class Module(core.module.Module): except Exception: self.__ip = None + if self.__show_country_name or self.__show_all: + try: + self.__country_name = util.location.country_name() + except Exception: + self.__country_name = None -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + if self.__show_country_code or self.__show_all: + try: + self.__country_code = util.location.country_code() + except Exception: + self.__country_code = None + + if self.__show_city_name or self.__show_all: + try: + self.__city_name = util.location.city_name() + except Exception: + self.__city_name = None + + if self.__show_coordinates or self.__show_all: + try: + __tmp = util.location.coordinates() + __lat = "{:.2f}".format(__tmp[0]) + __lon = "{:.2f}".format(__tmp[1]) + __output = __lat + "°N" + "," + " " + __lon + "°E" + self.__coordinates = __output + except Exception: + self.__city_name = None + + +# vim: tabstop=7 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/util/location.py b/bumblebee_status/util/location.py index e48b71a..840baa0 100644 --- a/bumblebee_status/util/location.py +++ b/bumblebee_status/util/location.py @@ -3,8 +3,10 @@ service and caches it for 12h (retries are done every 30m in case of problems) Right now, it uses (in order of preference): - - http://free.ipwhois.io/ - - http://ipapi.co/ + - http://free.ipwhois.io/ - 10k free requests/month + - http://ipapi.co/ - 30k free requests/month + - http://ip-api.com/ - ~2m free requests/month + """ @@ -21,7 +23,9 @@ __sources = [ "mapping": { "latitude": "latitude", "longitude": "longitude", - "country_name": "country", + "country_name": "country_name", + "country_code": "country_code", + "city": "city_name", "ip": "public_ip", }, }, @@ -30,9 +34,22 @@ __sources = [ "mapping": { "latitude": "latitude", "longitude": "longitude", - "country": "country", + "country": "country_name", + "country_code": "country_code", + "city": "city_name", "ip": "public_ip", }, + }, + { + "url": "http://ip-api.com/json", + "mapping": { + "latitude": "lat", + "longitude": "lon", + "country": "country_name", + "countryCode": "country_code", + "city": "city_name", + "query": "public_ip", + }, } ] @@ -63,7 +80,10 @@ def __get(name): global __data if not __data or __expired(): __load() - return __data[name] + if name in __data: + return __data[name] + else: + return None def reset(): @@ -82,14 +102,29 @@ def coordinates(): return __get("latitude"), __get("longitude") -def country(): +def country_name(): """Returns the current country name :return: country name :rtype: string """ - return __get("country") + return __get("country_name") +def country_code(): + """Returns the current country code + + :return: country code + :rtype: string + """ + return __get("country_code") + +def city_name(): + """Returns the current city name + + :return: city name + :rtype: string + """ + return __get("city_name") def public_ip(): """Returns the current public IP From 6f137c4927e6a0f005f95898d08487585fb56ba9 Mon Sep 17 00:00:00 2001 From: Tom Watson Date: Wed, 6 Jul 2022 17:51:19 +0700 Subject: [PATCH 2/3] Update following PR review Moved to format string handling of parameters. Minor refactoring. --- bumblebee_status/modules/contrib/publicip.py | 149 ++++++------------- 1 file changed, 46 insertions(+), 103 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index 5b061f4..f65e3b7 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -1,27 +1,31 @@ """ -Displays public IP address and, optionally, Country Name, Country Code & City Name +Displays zero or more of: + * Public IP address + * Country Name + * Country Code + * City Name + * Geographic Coordinates\ + Maximum refresh interval should be 5 minutes to avoid free SLA breach from providers Note: 1 request/5 minutes is 8640 requests/month Provider information contained in core.location -Left mouse click forces immediate update -Parameters (Default in brackets)_ -ip (True) Public IP address -country_name (False) Display name of country associated with the IP -country_code (False) Display country code of country associated with the IP -city_name (False) Display name of city associated with the IP -coordinates (False) Display name of city associated with the IP -all (False) Display all information associate with the IP +Left mouse click on the widget forces immediate update -Examples -By default only the public IP is shown -bumblebee-status -m publicip +Parameters: +publicip.format: Format string (defaults to ‘{ip} ({country_code})’) -To also include the country name... -bumblebee-status -m publicip -p publicip.country_name=True - -To include all ava +Available format strings: +ip +country_name +country_code +city_name +coordinates +Examples: +bumblebee-status -m publicip -p publicip.format="{ip} ({country_code})" +bumblebee-status -m publicip -p publicip.format="{ip} which is in {city_name}" +bumblebee-status -m publicip -p publicip.format="Your packets are right here: {coordinates}" """ import core.module @@ -37,107 +41,46 @@ class Module(core.module.Module): def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.public_ip)) + # Immediate update (override default) when left click on widget core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__click_update) - self.__ip = "" # Public IP address - self.__country_name = "" # Country name associated with public IP address - self.__country_code = "" # Country code associated with public IP address - self.__city_name = "" # City name associated with public IP address - self.__coordinates = "" # Coordinated assoicated with public IP address + self.__ip = "" # Public IP address + self.__country_name = "" # Country name associated with public IP address + self.__country_code = "" # Country code associated with public IP address + self.__city_name = "" # City name associated with public IP address + self.__coordinates = "" # Coordinated assoicated with public IP address - # Handle failure to get IP information - self.__ip_error = False - - # Process option paramaters - self.__show_ip = util.format.asbool( - self.parameter("ip", True) - ) - self.__show_country_name = util.format.asbool( - self.parameter("country_name", False) - ) - self.__show_country_code = util.format.asbool( - self.parameter("country_code", False) - ) - self.__show_city_name = util.format.asbool(self.parameter("city_name", False)) - self.__show_coordinates = util.format.asbool( - self.parameter("coordinates", False) - ) - self.__show_all = util.format.asbool(self.parameter("all", False)) + # By default show: (<2 letter country code>) + self._format = self.parameter("format", "{ip} ({country_code})") def __click_update(self, event): util.location.reset() def public_ip(self, widget): - __output = "" - - if self.__ip: - if self.__show_ip or self.__show_all: - __output = self.__ip - self.__ip_error = False + if not self.__ip: + return "Error fetching IP" else: - self.__ip_error = True - __output = "Error Getting IP" - - if not self.__ip_error: - if self.__show_country_name or self.__show_all: - if self.__country_name: - __output += " " + self.__country_name - else: - __output += " " + "?" - - if self.__show_country_code or self.__show_all: - if self.__country_code: - __output += " " + "(" + self.__country_code + ")" - else: - __output += " " + "(?)" - - if self.__show_city_name or self.__show_all: - if self.__city_name: - __output += " " + self.__city_name - else: - __output += " " + "?" - - if self.__show_coordinates or self.__show_all: - if self.__coordinates: - __output += " " + self.__coordinates - else: - __output += " " + "?" - - return __output + return self._format.format( + ip=self.__ip, + country_name=self.__country_name, + country_code=self.__country_code, + city_name=self.__city_name, + coordinates=self.__coordinates, + ) def update(self): try: self.__ip = util.location.public_ip() + self.__country_name = util.location.country_name() + self.__country_code = util.location.country_code() + self.__city_name = util.location.city_name() + __lat, __lon = util.location.coordinates() + __lat = "{:.2f}".format(__lat) + __lon = "{:.2f}".format(__lon) + __output = __lat + "°N" + "," + " " + __lon + "°E" + self.__coordinates = __output except Exception: - self.__ip = None - - if self.__show_country_name or self.__show_all: - try: - self.__country_name = util.location.country_name() - except Exception: - self.__country_name = None - - if self.__show_country_code or self.__show_all: - try: - self.__country_code = util.location.country_code() - except Exception: - self.__country_code = None - - if self.__show_city_name or self.__show_all: - try: - self.__city_name = util.location.city_name() - except Exception: - self.__city_name = None - - if self.__show_coordinates or self.__show_all: - try: - __tmp = util.location.coordinates() - __lat = "{:.2f}".format(__tmp[0]) - __lon = "{:.2f}".format(__tmp[1]) - __output = __lat + "°N" + "," + " " + __lon + "°E" - self.__coordinates = __output - except Exception: - self.__city_name = None + pass # vim: tabstop=7 expandtab shiftwidth=4 softtabstop=4 From a97a7fe507444b8f679b897ee4bf32f98590288a Mon Sep 17 00:00:00 2001 From: Tom Watson Date: Wed, 6 Jul 2022 19:37:29 +0700 Subject: [PATCH 3/3] Updates addressing PR comments Added location_info() to util/location API to return a dict of all location information. Updated modules/contrib/publicip to use that API. Changed modules/contrib/publicip refresh period back to 60 minutes. Changed /util/location API from 'country_name' back to 'name' --- bumblebee_status/modules/contrib/publicip.py | 16 ++++++------ bumblebee_status/util/location.py | 26 +++++++++++++++++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index f65e3b7..d043fce 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -37,7 +37,7 @@ import util.location class Module(core.module.Module): - @core.decorators.every(minutes=5) + @core.decorators.every(minutes=60) def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.public_ip)) @@ -48,7 +48,7 @@ class Module(core.module.Module): self.__country_name = "" # Country name associated with public IP address self.__country_code = "" # Country code associated with public IP address self.__city_name = "" # City name associated with public IP address - self.__coordinates = "" # Coordinated assoicated with public IP address + self.__coordinates = "" # Coordinates assoicated with public IP address # By default show: (<2 letter country code>) self._format = self.parameter("format", "{ip} ({country_code})") @@ -70,11 +70,13 @@ class Module(core.module.Module): def update(self): try: - self.__ip = util.location.public_ip() - self.__country_name = util.location.country_name() - self.__country_code = util.location.country_code() - self.__city_name = util.location.city_name() - __lat, __lon = util.location.coordinates() + __info = util.location.location_info() + self.__ip = __info["public_ip"] + self.__country_name = __info["country"] + self.__country_code = __info["country_code"] + self.__city_name = __info["city_name"] + __lat = __info["latitude"] + __lon = __info["longitude"] __lat = "{:.2f}".format(__lat) __lon = "{:.2f}".format(__lon) __output = __lat + "°N" + "," + " " + __lon + "°E" diff --git a/bumblebee_status/util/location.py b/bumblebee_status/util/location.py index 840baa0..1974773 100644 --- a/bumblebee_status/util/location.py +++ b/bumblebee_status/util/location.py @@ -50,7 +50,7 @@ __sources = [ "city": "city_name", "query": "public_ip", }, - } + }, ] @@ -87,8 +87,7 @@ def __get(name): def reset(): - """Resets the location library, ensuring that a new query will be started - """ + """Resets the location library, ensuring that a new query will be started""" global __next __next = 0 @@ -102,7 +101,7 @@ def coordinates(): return __get("latitude"), __get("longitude") -def country_name(): +def country(): """Returns the current country name :return: country name @@ -110,6 +109,7 @@ def country_name(): """ return __get("country_name") + def country_code(): """Returns the current country code @@ -118,6 +118,7 @@ def country_code(): """ return __get("country_code") + def city_name(): """Returns the current city name @@ -126,6 +127,7 @@ def city_name(): """ return __get("city_name") + def public_ip(): """Returns the current public IP @@ -135,4 +137,20 @@ def public_ip(): return __get("public_ip") +def location_info(): + """Returns the current location information + + :return: public IP, country name, country code, city name & coordinates + :rtype: dictionary + """ + return { + "public_ip": __get("public_ip"), + "country": __get("country_name"), + "country_code": __get("country_code"), + "city_name": __get("city_name"), + "latitude": __get("latitude"), + "longitude": __get("longitude"), + } + + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4