diff --git a/clients/discord.py b/clients/discord.py index 5759c87..e69228a 100644 --- a/clients/discord.py +++ b/clients/discord.py @@ -65,6 +65,8 @@ class DiscordClient(discord.Client): (re.compile(r'^!cheese\s+(?P")?(?P.+)(?(q)")$'), self._do_cheese), (re.compile(r'^!roll(\s+(?P")?(?P.+)(?(q)"))?$'), self._do_roll), (re.compile(r'^!nextstream$'), self._do_nextstream), + (re.compile(r'^!conv(ert)?\s+(?P")?(?P.+?)(?(q1)")' + r'(\s+(?P")?(?P[^\s]+)(?(q2)"))?$'), self._do_convert), ] super(DiscordClient, self).__init__() @@ -287,3 +289,12 @@ class DiscordClient(discord.Client): embed.add_field(name=format_delta(time - datetime.datetime.utcnow()), value=time.strftime('%Y-%m-%d %I:%M %p GMT'), inline=False) await message.channel.send(embed=embed) + + async def _do_convert(self, server, user, message, expression, unit, **kwargs): + try: + result = self.commands.convert(expression, unit) + except CommandError as e: + await message.channel.send('Sorry {0}, {1}'.format(message.author.mention, e)) + else: + embed = discord.Embed(description='**{0}**'.format(result), color=0x39ad0f) + await message.channel.send(embed=embed) diff --git a/clients/twitch.py b/clients/twitch.py index 1fee551..0c1509f 100644 --- a/clients/twitch.py +++ b/clients/twitch.py @@ -105,6 +105,8 @@ class TwitchClient(irc.bot.SingleServerIRCBot): (re.compile(r'^!(find)?clip\s+(?P")?(?P.+)(?(q)")$'), self._do_clip), (re.compile(r'^!roll(\s+(?P")?(?P.+)(?(q)"))?$'), self._do_roll), (re.compile(r'^!giveaway$'), self._do_giveaway), + (re.compile(r'^!conv(ert)?\s+(?P")?(?P.+?)(?(q1)")' + r'(\s+(?P")?(?P[^\s]+)(?(q2)"))?$'), self._do_convert), (re.compile(r'^!command\s+set\s+(?P")?(?P.+?)(?(q1)")\s+' r'(?P")?(?P.+)(?(q2)")$'), self._do_command_set), (re.compile(r'^!command\s+unset\s+(?P")?(?P.+)(?(q)")$'), self._do_command_unset), @@ -342,6 +344,14 @@ class TwitchClient(irc.bot.SingleServerIRCBot): if self.giveaway['command']: send_response('Type {0} to join!'.format(self.giveaway['command'])) + def _do_convert(self, tags, send_response, expression, unit, **kwargs): + try: + result = self.commands.convert(expression, unit) + except CommandError as e: + send_response('Sorry @{0}, {1}'.format(tags['display-name'], e)) + else: + send_response(result) + def _do_command_set(self, tags, send_response, cmd, resp, **kwargs): if not self._is_mod(tags): send_response('Sorry @{0}, you are not allowed to do this'.format(tags['display-name'])) diff --git a/commands.py b/commands.py index 90d2ac6..cc9e2f0 100644 --- a/commands.py +++ b/commands.py @@ -4,9 +4,10 @@ import unicodedata import dateutil.parser import requests -from services.youtube import Youtube, YoutubeError from services.cheesecom import CheeseCom, CheeseComError +from services.converter import Converter, ConverterError from services.roll20 import Roll20, Roll20Error +from services.youtube import Youtube, YoutubeError INSTAGRAM_BASE_URL = 'https://www.instagram.com' @@ -210,6 +211,13 @@ class Commands(object): self.last_roll_formula = formula return result + def convert(self, expression, unit=None): + c = Converter() + try: + return c.convert(expression, unit) + except ConverterError as e: + raise CommandError(str(e)) + def _get_instagram_media(self, params): api_url = self.config['Instagram'].get('api_url') r = requests.get('{0}/media'.format(api_url), params=params) diff --git a/requirements.txt b/requirements.txt index 2c2bd37..04d5cfa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ fuzzywuzzy[speedup] google-api-python-client irc parsy +pint pyquery python-dateutil python-twitter diff --git a/services/converter.py b/services/converter.py new file mode 100644 index 0000000..82a5cab --- /dev/null +++ b/services/converter.py @@ -0,0 +1,65 @@ +import pint + + +class ConverterError(Exception): + pass + + +class Converter(object): + def __init__(self): + self.ureg = pint.UnitRegistry(autoconvert_offset_to_baseunit=True) + self.ureg.define('degreeC = kelvin; offset: 273.15 = °C = C') + self.ureg.define('degreeF = 5 / 9 * kelvin; offset: 255.372222 = °F = F') + self.default_units = { + self.ureg.degC: self.ureg.degreeF, + self.ureg.degreeC: self.ureg.degreeF, + self.ureg.degF: self.ureg.degreeC, + self.ureg.degreeF: self.ureg.degreeC, + self.ureg.kilometer: self.ureg.mile, + self.ureg.meter: self.ureg.foot, + self.ureg.centimeter: self.ureg.inch, + self.ureg.millimeter: self.ureg.inch, + self.ureg.mile: self.ureg.kilometer, + self.ureg.foot: self.ureg.meter, + self.ureg.inch: self.ureg.millimeter, + self.ureg.kilogram: self.ureg.pound, + self.ureg.pound: self.ureg.kilogram, + self.ureg.ounce: self.ureg.kilogram, + self.ureg.liter: self.ureg.gallon, + self.ureg.milliliter: self.ureg.floz, + self.ureg.gallon: self.ureg.liter, + self.ureg.floz: self.ureg.milliliter, + } + + def convert(self, expression, unit=None): + try: + q = self.ureg.parse_expression(expression) + except pint.errors.UndefinedUnitError: + raise ConverterError('unknown units') + try: + q.units + except AttributeError: + if unit is not None: + q = self.ureg.parse_expression(expression + unit) + unit = None + else: + raise ConverterError('unknown units') + try: + q.units + except AttributeError: + raise ConverterError('unknown units') + if unit is not None: + try: + u = self.ureg.parse_units(unit) + except pint.errors.UndefinedUnitError: + raise ConverterError('unknown units') + else: + try: + u = self.default_units[q.units] + except KeyError: + raise ConverterError('no target units specified') + try: + result = q.to(u) + except pint.errors.DimensionalityError: + raise ConverterError('invalid conversion') + return '{:0.5g~P}'.format(result)