interactive bots: Create converter bot.
This commit is contained in:
parent
f563654377
commit
a86066c386
70
contrib_bots/lib/ConverterBot/docs.md
Normal file
70
contrib_bots/lib/ConverterBot/docs.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Converter bot
|
||||
|
||||
This bot allows users to perform conversions for various measurement units.
|
||||
|
||||
## Usage
|
||||
|
||||
Run this bot as described in [here](http://zulip.readthedocs.io/en/latest/bots-guide.html#how-to-deploy-a-bot).
|
||||
|
||||
Use this bot with the following command
|
||||
|
||||
`@convert <number> <unit_from> <unit_to>`
|
||||
|
||||
This will convert `number`, given in the unit `unit_from`, to the unit `unit_to`
|
||||
and print the result.
|
||||
|
||||
* `number` can be any floating-point number, e.g. 12, 13.05, 0.002.
|
||||
* `unit_from` and `unit_to` are two units from [the following](#supported-units) table in the same category.
|
||||
* `unit_from` and `unit_to` can be preceded by [these](#supported-prefixes) prefixes.
|
||||
|
||||
### Supported units
|
||||
|
||||
| Category | Units |
|
||||
| ----------------- | ----- |
|
||||
| Area | square-centimeter (cm^2, cm2), square-decimeter (dm^2, dm2), square-meter (m^2, m2), square-kilometer (km^2, km2), square-inch (in^2, in2), square-foot (ft^2, ft2), square-yard (y^2, y2), square-mile (mi^2, mi2), are (a), hectare (ha), acre (ac) |
|
||||
| Information | bit, byte |
|
||||
| Length | centimeter (cm), decimeter (dm), meter (m), kilometer (km), inch (in), foot (ft), yard (y), mile (mi), nautical-mile (nmi) |
|
||||
| Temperature | Kelvin (K), Celsius (C), Fahrenheit (F) |
|
||||
| Volume | cubic-centimeter (cm^3, cm3), cubic-decimeter (dm^3, dm3), liter (l), cubic-meter (m^3, m3), cubic-inch (in^3, in3), fluid-ounce (fl-oz), cubic-foot (ft^3, ft3), cubic-yard (y^3, y3) |
|
||||
| Weight | gram (g), kilogram (kg), ton (t), ounce (oz), pound (lb) |
|
||||
| Cooking (metric only, U.S. and imperial units differ slightly) | teaspoon (tsp), tablespoon (tbsp), cup |
|
||||
|
||||
### Supported prefixes
|
||||
|
||||
| Prefix | Power of 10 |
|
||||
| ------ | ----------- |
|
||||
| atto | 10<sup>-18</sup> |
|
||||
| pico | 10<sup>-15</sup> |
|
||||
| femto | 10<sup>-12</sup> |
|
||||
| nano | 10<sup>-9</sup> |
|
||||
| micro | 10<sup>-6</sup> |
|
||||
| milli | 10<sup>-3</sup> |
|
||||
| centi | 10<sup>-2</sup> |
|
||||
| deci | 10<sup>-1</sup> |
|
||||
| deca | 10<sup>1</sup> |
|
||||
| hecto | 10<sup>2</sup> |
|
||||
| kilo | 10<sup>3</sup> |
|
||||
| mega | 10<sup>6</sup> |
|
||||
| giga | 10<sup>9</sup> |
|
||||
| tera | 10<sup>12</sup> |
|
||||
| peta | 10<sup>15</sup> |
|
||||
| exa | 10<sup>18</sup> |
|
||||
|
||||
### Usage examples
|
||||
|
||||
| Message | Response |
|
||||
| ------- | ------ |
|
||||
| `@convert 12 celsius fahrenheit` | 12.0 celsius = 53.600054 fahrenheit |
|
||||
| `@convert 0.002 kilomile millimeter` | 0.002 kilomile = 3218688.0 millimeter |
|
||||
| `@convert 31.5 square-mile ha | 31.5 square-mile = 8158.4625 ha |
|
||||
| `@convert 56 g lb` | 56.0 g = 0.12345887 lb |
|
||||
|
||||
## Notes
|
||||
|
||||
* You can use multiple `@convert` statements in a message, the response will look accordingly:
|
||||
![multiple-converts](multiple-converts.png)
|
||||
|
||||
* Enter `@convert help` to display a quick overview of the converter's functionality.
|
||||
|
||||
* For bits and bytes, the prefixes change the figure differently: 1 kilobyte is 1024 bytes,
|
||||
1 megabyte is 1048576 bytes, etc.
|
BIN
contrib_bots/lib/ConverterBot/multiple-converts.png
Normal file
BIN
contrib_bots/lib/ConverterBot/multiple-converts.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
275
contrib_bots/lib/converter.py
Normal file
275
contrib_bots/lib/converter.py
Normal file
|
@ -0,0 +1,275 @@
|
|||
# See readme.md for instructions on running this code.
|
||||
|
||||
from __future__ import division
|
||||
from past.utils import old_div
|
||||
|
||||
import copy
|
||||
from math import log10, floor
|
||||
|
||||
# A dictionary allowing the conversion of each unit to its base unit.
|
||||
# An entry consists of the unit's name, a constant number and a constant
|
||||
# factor that need to be added and multiplied to convert the unit into
|
||||
# the base unit in the last parameter.
|
||||
UNITS = {'bit': [0, 1, 'bit'],
|
||||
'byte': [0, 8, 'bit'],
|
||||
'cubic-centimeter': [0, 0.000001, 'cubic-meter'],
|
||||
'cubic-decimeter': [0, 0.001, 'cubic-meter'],
|
||||
'liter': [0, 0.001, 'cubic-meter'],
|
||||
'cubic-meter': [0, 1, 'cubic-meter'],
|
||||
'cubic-inch': [0, 0.000016387064, 'cubic-meter'],
|
||||
'fluid-ounce': [0, 0.000029574, 'cubic-meter'],
|
||||
'cubic-foot': [0, 0.028316846592, 'cubic-meter'],
|
||||
'cubic-yard': [0, 0.764554857984, 'cubic-meter'],
|
||||
'teaspoon': [0, 0.0000049289216, 'cubic-meter'],
|
||||
'tablespoon': [0, 0.000014787, 'cubic-meter'],
|
||||
'cup': [0, 0.00023658823648491, 'cubic-meter'],
|
||||
'gram': [0, 1, 'gram'],
|
||||
'kilogram': [0, 1000, 'gram'],
|
||||
'ton': [0, 1000000, 'gram'],
|
||||
'ounce': [0, 28.349523125, 'gram'],
|
||||
'pound': [0, 453.59237, 'gram'],
|
||||
'kelvin': [0, 1, 'kelvin'],
|
||||
'celsius': [273.15, 1, 'kelvin'],
|
||||
'fahrenheit': [255.372222, 0.555555, 'kelvin'],
|
||||
'centimeter': [0, 0.01, 'meter'],
|
||||
'decimeter': [0, 0.1, 'meter'],
|
||||
'meter': [0, 1, 'meter'],
|
||||
'kilometer': [0, 1000, 'meter'],
|
||||
'inch': [0, 0.0254, 'meter'],
|
||||
'foot': [0, 0.3048, 'meter'],
|
||||
'yard': [0, 0.9144, 'meter'],
|
||||
'mile': [0, 1609.344, 'meter'],
|
||||
'nautical-mile': [0, 1852, 'meter'],
|
||||
'square-centimeter': [0, 0.0001, 'square-meter'],
|
||||
'square-decimeter': [0, 0.01, 'square-meter'],
|
||||
'square-meter': [0, 1, 'square-meter'],
|
||||
'square-kilometer': [0, 1000000, 'square-meter'],
|
||||
'square-inch': [0, 0.00064516, 'square-meter'],
|
||||
'square-foot': [0, 0.09290304, 'square-meter'],
|
||||
'square-yard': [0, 0.83612736, 'square-meter'],
|
||||
'square-mile': [0, 2589988.110336, 'square-meter'],
|
||||
'are': [0, 100, 'square-meter'],
|
||||
'hectare': [0, 10000, 'square-meter'],
|
||||
'acre': [0, 4046.8564224, 'square-meter']}
|
||||
|
||||
PREFIXES = {'atto': -18,
|
||||
'femto': -15,
|
||||
'pico': -12,
|
||||
'nano': -9,
|
||||
'micro': -6,
|
||||
'milli': -3,
|
||||
'centi': -2,
|
||||
'deci': -1,
|
||||
'deca': 1,
|
||||
'hecto': 2,
|
||||
'kilo': 3,
|
||||
'mega': 6,
|
||||
'giga': 9,
|
||||
'tera': 12,
|
||||
'peta': 15,
|
||||
'exa': 18}
|
||||
|
||||
ALIASES = {'a': 'are',
|
||||
'ac': 'acre',
|
||||
'c': 'celsius',
|
||||
'cm': 'centimeter',
|
||||
'cm2': 'square-centimeter',
|
||||
'cm3': 'cubic-centimeter',
|
||||
'cm^2': 'square-centimeter',
|
||||
'cm^3': 'cubic-centimeter',
|
||||
'dm': 'decimeter',
|
||||
'dm2': 'square-decimeter',
|
||||
'dm3': 'cubic-decimeter',
|
||||
'dm^2': 'square-decimeter',
|
||||
'dm^3': 'cubic-decimeter',
|
||||
'f': 'fahrenheit',
|
||||
'fl-oz': 'fluid-ounce',
|
||||
'ft': 'foot',
|
||||
'ft2': 'square-foot',
|
||||
'ft3': 'cubic-foot',
|
||||
'ft^2': 'square-foot',
|
||||
'ft^3': 'cubic-foot',
|
||||
'g': 'gram',
|
||||
'ha': 'hectare',
|
||||
'in': 'inch',
|
||||
'in2': 'square-inch',
|
||||
'in3': 'cubic-inch',
|
||||
'in^2': 'square-inch',
|
||||
'in^3': 'cubic-inch',
|
||||
'k': 'kelvin',
|
||||
'kg': 'kilogram',
|
||||
'km': 'kilometer',
|
||||
'km2': 'square-kilometer',
|
||||
'km^2': 'square-kilometer',
|
||||
'l': 'liter',
|
||||
'lb': 'pound',
|
||||
'm': 'meter',
|
||||
'm2': 'square-meter',
|
||||
'm3': 'cubic-meter',
|
||||
'm^2': 'square-meter',
|
||||
'm^3': 'cubic-meter',
|
||||
'mi': 'mile',
|
||||
'mi2': 'square-mile',
|
||||
'mi^2': 'square-mile',
|
||||
'nmi': 'nautical-mile',
|
||||
'oz': 'ounce',
|
||||
't': 'ton',
|
||||
'tbsp': 'tablespoon',
|
||||
'tsp': 'teaspoon',
|
||||
'y': 'yard',
|
||||
'y2': 'square-yard',
|
||||
'y3': 'cubic-yard',
|
||||
'y^2': 'square-yard',
|
||||
'y^3': 'cubic-yard'}
|
||||
|
||||
HELP_MESSAGE = ('Converter usage:\n'
|
||||
'`@convert <number> <unit_from> <unit_to>`\n'
|
||||
'Converts `number` in the unit <unit_from> to '
|
||||
'the <unit_to> and prints the result\n'
|
||||
'`number`: integer or floating point number, e.g. 12, 13.05, 0.002\n'
|
||||
'<unit_from> and <unit_to> are two of the following units:\n'
|
||||
'* square-centimeter (cm^2, cm2), square-decimeter (dm^2, dm2), '
|
||||
'square-meter (m^2, m2), square-kilometer (km^2, km2),'
|
||||
' square-inch (in^2, in2), square-foot (ft^2, ft2), square-yard (y^2, y2), '
|
||||
' square-mile(mi^2, mi2), are (a), hectare (ha), acre (ac)\n'
|
||||
'* bit, byte\n'
|
||||
'* centimeter (cm), decimeter(dm), meter (m),'
|
||||
' kilometer (km), inch (in), foot (ft), yard (y),'
|
||||
' mile (mi), nautical-mile (nmi)\n'
|
||||
'* Kelvin (K), Celsius(C), Fahrenheit (F)\n'
|
||||
'* cubic-centimeter (cm^3, cm3), cubic-decimeter (dm^3, dm3), liter (l), '
|
||||
'cubic-meter (m^3, m3), cubic-inch (in^3, in3), fluid-ounce (fl-oz), '
|
||||
'cubic-foot (ft^3, ft3), cubic-yard (y^3, y3)\n'
|
||||
'* gram (g), kilogram (kg), ton (t), ounce (oz), pound(lb)\n'
|
||||
'* (metric only, U.S. and imperial units differ slightly:) teaspoon (tsp), tablespoon (tbsp), cup\n\n\n'
|
||||
'Allowed prefixes are:\n'
|
||||
'* atto, pico, femto, nano, micro, milli, centi, deci\n'
|
||||
'* deca, hecto, kilo, mega, giga, tera, peta, exa\n\n\n'
|
||||
'Usage examples:\n'
|
||||
'* `@convert 12 celsius fahrenheit`\n'
|
||||
'* `@convert 0.002 kilomile millimeter`\n'
|
||||
'* `@convert 31.5 square-mile ha`\n'
|
||||
'* `@convert 56 g lb`\n')
|
||||
|
||||
QUICK_HELP = 'Enter `@convert help` for help on using the converter.'
|
||||
|
||||
def is_float(value):
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
# Rounds the number 'x' to 'digits' significant digits.
|
||||
# A normal 'round()' would round the number to an absolute amount of
|
||||
# fractional decimals, e.g. 0.00045 would become 0.0.
|
||||
# 'round_to()' rounds only the digits that are not 0.
|
||||
# 0.00045 would then become 0.0005.
|
||||
def round_to(x, digits):
|
||||
return round(x, digits-int(floor(log10(abs(x)))))
|
||||
|
||||
class ConverterHandler(object):
|
||||
'''
|
||||
This plugin allows users to make conversions between various units,
|
||||
e.g. Celsius to Fahrenheit, or kilobytes to gigabytes.
|
||||
It looks for messages of the format
|
||||
'@convert <number> <unit_from> <unit_to>'
|
||||
The message '@convert help' posts a short description of how to use
|
||||
the plugin, along with a list of all supported units.
|
||||
'''
|
||||
|
||||
def usage(self):
|
||||
return '''
|
||||
This plugin allows users to make conversions between
|
||||
various units, e.g. Celsius to Fahrenheit,
|
||||
or kilobytes to gigabytes. It looks for messages of
|
||||
the format '@convert <number> <unit_from> <unit_to>'
|
||||
The message '@convert help' posts a short description of
|
||||
how to use the plugin, along with a list of
|
||||
all supported units.
|
||||
'''
|
||||
|
||||
def triage_message(self, message, client):
|
||||
return '@convert' in message['content']
|
||||
|
||||
def handle_message(self, message, client, state_handler):
|
||||
content = message['content']
|
||||
|
||||
words = content.lower().split()
|
||||
convert_indexes = [i for i, word in enumerate(words) if word == "@convert"]
|
||||
results = []
|
||||
|
||||
for convert_index in convert_indexes:
|
||||
if (convert_index + 1) < len(words) and words[convert_index + 1] == 'help':
|
||||
results.append(HELP_MESSAGE)
|
||||
continue
|
||||
if (convert_index + 3) < len(words):
|
||||
number = words[convert_index + 1]
|
||||
unit_from = ALIASES.get(words[convert_index + 2], words[convert_index + 2])
|
||||
unit_to = ALIASES.get(words[convert_index + 3], words[convert_index + 3])
|
||||
exponent = 0
|
||||
|
||||
if not is_float(number):
|
||||
results.append(number + ' is not a valid number. ' + QUICK_HELP)
|
||||
continue
|
||||
|
||||
number = float(number)
|
||||
number_res = copy.copy(number)
|
||||
|
||||
for key, exp in PREFIXES.items():
|
||||
if unit_from.startswith(key):
|
||||
exponent += exp
|
||||
unit_from = unit_from[len(key):]
|
||||
if unit_to.startswith(key):
|
||||
exponent -= exp
|
||||
unit_to = unit_to[len(key):]
|
||||
|
||||
uf_to_std = UNITS.get(unit_from, False)
|
||||
ut_to_std = UNITS.get(unit_to, False)
|
||||
|
||||
if uf_to_std is False:
|
||||
results.append(unit_from + ' is not a valid unit. ' + QUICK_HELP)
|
||||
if ut_to_std is False:
|
||||
results.append(unit_to + ' is not a valid unit.' + QUICK_HELP)
|
||||
if uf_to_std is False or ut_to_std is False:
|
||||
continue
|
||||
|
||||
base_unit = uf_to_std[2]
|
||||
if uf_to_std[2] != ut_to_std[2]:
|
||||
unit_from = unit_from.capitalize() if uf_to_std[2] == 'kelvin' else unit_from
|
||||
results.append(unit_to.capitalize() + ' and ' + unit_from +
|
||||
' are not from the same category. ' + QUICK_HELP)
|
||||
continue
|
||||
|
||||
# perform the conversion between the units
|
||||
number_res *= uf_to_std[1]
|
||||
number_res += uf_to_std[0]
|
||||
number_res -= ut_to_std[0]
|
||||
number_res /= ut_to_std[1]
|
||||
|
||||
if base_unit == 'bit':
|
||||
number_res *= 1024 ** (old_div(exponent, float(3)))
|
||||
else:
|
||||
number_res *= 10 ** exponent
|
||||
number_res = round_to(number_res, 7)
|
||||
|
||||
results.append('{} {} = {} {}'.format(number,
|
||||
words[convert_index + 2],
|
||||
number_res,
|
||||
words[convert_index + 3]))
|
||||
|
||||
else:
|
||||
results.append('Too few arguments given. ' + QUICK_HELP)
|
||||
|
||||
new_content = ''
|
||||
for idx, result in enumerate(results, 1):
|
||||
new_content += ((str(idx) + '. conversion: ') if len(results) > 1 else '') + result + '\n'
|
||||
|
||||
client.send_message(dict(
|
||||
type='stream',
|
||||
to=message['display_recipient'],
|
||||
subject=message['subject'],
|
||||
content=new_content,
|
||||
))
|
||||
|
||||
handler_class = ConverterHandler
|
Loading…
Reference in a new issue