WolframAlpha: float btc input/output

This commit is contained in:
oddluck 2020-06-23 15:21:22 +00:00
parent 5b9997ff9e
commit a51bc196da
4 changed files with 220 additions and 101 deletions

View File

@ -38,19 +38,22 @@ import importlib
# Use this for the version of this plugin. You may wish to put a CVS keyword # Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system. # in here if you're keeping the plugin in CVS or some similar system.
__version__ = "2020.02.24+git" __version__ = "2020.06.23+git"
# XXX Replace this with an appropriate author or supybot.Author instance. # XXX Replace this with an appropriate author or supybot.Author instance.
__author__ = supybot.Author('reticulatingspline', 'spline', '') __author__ = supybot.Author("reticulatingspline", "spline", "")
__maintainer__ = getattr(supybot.authors, 'oddluck', __maintainer__ = getattr(
supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) supybot.authors,
"oddluck",
supybot.Author("oddluck", "oddluck", "oddluck@riseup.net"),
)
# This is a dictionary mapping supybot.Author instances to lists of # This is a dictionary mapping supybot.Author instances to lists of
# contributions. # contributions.
__contributors__ = {} __contributors__ = {}
# This is a url where the most recent plugin package can be downloaded. # This is a url where the most recent plugin package can be downloaded.
__url__ = 'https://github.com/oddluck/limnoria-plugins/' __url__ = "https://github.com/oddluck/limnoria-plugins/"
from . import config from . import config
from . import plugin from . import plugin

View File

@ -30,28 +30,55 @@
import supybot.conf as conf import supybot.conf as conf
import supybot.registry as registry import supybot.registry as registry
try: try:
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('WolframAlpha')
_ = PluginInternationalization("WolframAlpha")
except: except:
# Placeholder that allows to run the plugin on a bot # Placeholder that allows to run the plugin on a bot
# without the i18n module # without the i18n module
_ = lambda x: x _ = lambda x: x
def configure(advanced): def configure(advanced):
# This will be called by supybot to configure this module. advanced is # This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified himself as an advanced # a bool that specifies whether the user identified himself as an advanced
# user or not. You should effect your configuration by manipulating the # user or not. You should effect your configuration by manipulating the
# registry as appropriate. # registry as appropriate.
from supybot.questions import expect, anything, something, yn from supybot.questions import expect, anything, something, yn
conf.registerPlugin('WolframAlpha', True)
conf.registerPlugin("WolframAlpha", True)
WolframAlpha = conf.registerPlugin('WolframAlpha') WolframAlpha = conf.registerPlugin("WolframAlpha")
conf.registerGlobalValue(WolframAlpha, 'apiKey', registry.String('', ("""Your Wolfram Alpha API key."""), private=True)) conf.registerGlobalValue(
conf.registerGlobalValue(WolframAlpha, 'maxOutput', registry.Integer(4, ("""How many lines by default to output."""))) WolframAlpha,
conf.registerGlobalValue(WolframAlpha, 'useImperial', registry.Boolean(True, ("""Use imperial units? Defaults to yes."""))) "apiKey",
conf.registerGlobalValue(WolframAlpha, 'reinterpretInput', registry.Boolean(False, ("""Reinterpret input string if WA API cannot understand. Best to leave false."""))) registry.String("", """Your Wolfram Alpha API key.""", private=True),
conf.registerChannelValue(WolframAlpha, 'disableANSI', registry.Boolean(False, """Do not display any ANSI (color/bold) for channel.""")) )
conf.registerGlobalValue(
WolframAlpha,
"maxOutput",
registry.Integer(4, """How many lines by default to output."""),
)
conf.registerGlobalValue(
WolframAlpha,
"useImperial",
registry.Boolean(True, """Use imperial units? Defaults to yes."""),
)
conf.registerGlobalValue(
WolframAlpha,
"reinterpretInput",
registry.Boolean(
False,
"""Reinterpret input string if WA API cannot understand. Best to leave false.""",
),
)
conf.registerChannelValue(
WolframAlpha,
"disableANSI",
registry.Boolean(False, """Do not display any ANSI (color/bold) for channel."""),
)
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=250: # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=250:

View File

@ -30,6 +30,7 @@
# my libs # my libs
from collections import defaultdict from collections import defaultdict
try: try:
import xml.etree.cElementTree as ElementTree import xml.etree.cElementTree as ElementTree
except ImportError: except ImportError:
@ -41,17 +42,21 @@ import supybot.plugins as plugins
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
import re import re
try: try:
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('WolframAlpha')
_ = PluginInternationalization("WolframAlpha")
except: except:
# Placeholder that allows to run the plugin on a bot # Placeholder that allows to run the plugin on a bot
# without the i18n module # without the i18n module
_ = lambda x: x _ = lambda x: x
class WolframAlpha(callbacks.Plugin): class WolframAlpha(callbacks.Plugin):
"""Add the help for "@plugin help WolframAlpha" here """Add the help for "@plugin help WolframAlpha" here
This should describe *how* to use this plugin.""" This should describe *how* to use this plugin."""
threaded = True threaded = True
###################### ######################
@ -59,7 +64,7 @@ class WolframAlpha(callbacks.Plugin):
###################### ######################
def _red(self, s): def _red(self, s):
return ircutils.mircColor(s, 'red') return ircutils.mircColor(s, "red")
def _bold(self, s): def _bold(self, s):
return ircutils.bold(s) return ircutils.bold(s)
@ -82,40 +87,47 @@ class WolframAlpha(callbacks.Plugin):
""" """
# check for API key before we can do anything. # check for API key before we can do anything.
apiKey = self.registryValue('apiKey') apiKey = self.registryValue("apiKey")
if not apiKey or apiKey == "Not set": if not apiKey or apiKey == "Not set":
irc.reply("Wolfram Alpha API key not set. see 'config help supybot.plugins.WolframAlpha.apiKey'.") irc.reply(
"Wolfram Alpha API key not set. see 'config help"
" supybot.plugins.WolframAlpha.apiKey'."
)
return return
# first, url arguments, some of which getopts and config variables can manipulate. # first, url arguments, some of which getopts and config variables can manipulate.
urlArgs = { 'input':optinput, urlArgs = {
'appid':apiKey, "input": optinput,
'reinterpret':'false', "appid": apiKey,
'format':'plaintext', "reinterpret": "false",
'units':'nonmetric' } "format": "plaintext",
"units": "nonmetric",
}
# check for config variables to manipulate URL arguments. # check for config variables to manipulate URL arguments.
if not self.registryValue('useImperial'): if not self.registryValue("useImperial"):
urlArgs['units'] = 'metric' urlArgs["units"] = "metric"
if self.registryValue('reinterpretInput'): if self.registryValue("reinterpretInput"):
urlArgs['reinterpret'] = 'true' urlArgs["reinterpret"] = "true"
# now handle input. default input arguments. # now handle input. default input arguments.
args = { 'maxoutput': self.registryValue('maxOutput'), args = {
'shortest':None, "maxoutput": self.registryValue("maxOutput"),
'fulloutput':None } "shortest": None,
"fulloutput": None,
}
# handle getopts (optlist) # handle getopts (optlist)
if optlist: if optlist:
for (key, value) in optlist: for (key, value) in optlist:
if key == 'shortest': if key == "shortest":
args['shortest'] = True args["shortest"] = True
if key == 'fulloutput': if key == "fulloutput":
args['fulloutput'] = True args["fulloutput"] = True
if key == 'num': if key == "num":
args['maxoutput'] = value args["maxoutput"] = value
if key == 'usemetric': if key == "usemetric":
urlArgs['units'] = 'metric' urlArgs["units"] = "metric"
if key == 'reinterpret': if key == "reinterpret":
urlArgs['reinterpret'] = 'true' urlArgs["reinterpret"] = "true"
# build url and query. # build url and query.
url = 'http://api.wolframalpha.com/v2/query?' + utils.web.urlencode(urlArgs) url = "http://api.wolframalpha.com/v2/query?" + utils.web.urlencode(urlArgs)
try: try:
page = utils.web.getUrl(url) page = utils.web.getUrl(url)
except Exception as e: except Exception as e:
@ -131,31 +143,45 @@ class WolframAlpha(callbacks.Plugin):
return return
# document = ElementTree.fromstring(page) #.decode('utf-8')) # document = ElementTree.fromstring(page) #.decode('utf-8'))
# check if we have an error. reports to irc but more detailed in the logs. # check if we have an error. reports to irc but more detailed in the logs.
if document.attrib['success'] == 'false' and document.attrib['error'] == 'true': if document.attrib["success"] == "false" and document.attrib["error"] == "true":
errormsgs = [] errormsgs = []
for error in document.findall('.//error'): for error in document.findall(".//error"):
errorcode = error.find('code').text errorcode = error.find("code").text
errormsg = error.find('msg').text errormsg = error.find("msg").text
errormsgs.append("{0} - {1}".format(errorcode, errormsg)) errormsgs.append("{0} - {1}".format(errorcode, errormsg))
# log and report to irc if we have these. # log and report to irc if we have these.
self.log.debug("ERROR processing request for: {0} message: {1}".format(optinput, errormsgs)) self.log.debug(
irc.reply("ERROR: Something went wrong processing request for: {0} ERROR: {1}".format(optinput, errormsgs)) "ERROR processing request for: {0} message: {1}".format(
optinput, errormsgs
)
)
irc.reply(
"ERROR: Something went wrong processing request for: {0} ERROR: {1}"
.format(optinput, errormsgs)
)
return return
# check if we have no success but also no error. (Did you mean?) # check if we have no success but also no error. (Did you mean?)
elif document.attrib['success'] == 'false' and document.attrib['error'] == 'false': elif (
document.attrib["success"] == "false"
and document.attrib["error"] == "false"
):
errormsgs = [] # list to contain whatever is there. errormsgs = [] # list to contain whatever is there.
for error in document.findall('.//futuretopic'): for error in document.findall(".//futuretopic"):
errormsg = error.attrib['msg'] errormsg = error.attrib["msg"]
errormsgs.append("FUTURE TOPIC: {0}".format(errormsg)) errormsgs.append("FUTURE TOPIC: {0}".format(errormsg))
for error in document.findall('.//didyoumeans'): for error in document.findall(".//didyoumeans"):
errormsg = error.find('didyoumean').text errormsg = error.find("didyoumean").text
errormsgs.append("Did you mean? {0}".format(errormsg)) errormsgs.append("Did you mean? {0}".format(errormsg))
for error in document.findall('.//tips'): for error in document.findall(".//tips"):
errormsg = error.find('tip').attrib['text'].text errormsg = error.find("tip").attrib["text"].text
errormsgs.append("TIPS: {0}".format(errormsg)) errormsgs.append("TIPS: {0}".format(errormsg))
# now output the messages to irc and log. # now output the messages to irc and log.
self.log.debug("ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)) self.log.debug(
irc.reply("ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)) "ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)
)
irc.reply(
"ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)
)
return return
else: # this means we have success and no error messages. else: # this means we have success and no error messages.
# each pod has a title, position and a number of subtexts. output contains the plaintext. # each pod has a title, position and a number of subtexts. output contains the plaintext.
@ -163,53 +189,109 @@ class WolframAlpha(callbacks.Plugin):
output = defaultdict(list) output = defaultdict(list)
outputlist = {} outputlist = {}
# each answer has a different amount of pods. # each answer has a different amount of pods.
for pod in document.findall('.//pod'): for pod in document.findall(".//pod"):
title = pod.attrib['title'] # title of it. title = pod.attrib["title"] # title of it.
position = int(pod.attrib['position']) # store pods int when we sort. position = int(pod.attrib["position"]) # store pods int when we sort.
outputlist[position] = title # pu outputlist[position] = title # pu
for plaintext in pod.findall('.//plaintext'): for plaintext in pod.findall(".//plaintext"):
if plaintext.text: if plaintext.text:
output[title].append(plaintext.text.replace('\n',' ')) output[title].append(plaintext.text.replace("\n", " "))
# last sanity check... # last sanity check...
if len(output) == 0: if len(output) == 0:
irc.reply("ERROR: I received no output looking up: {0}".format(optinput)) irc.reply("ERROR: I received no output looking up: {0}".format(optinput))
return return
# done processing the XML so lets work on the output. # done processing the XML so lets work on the output.
# the way we output is based on args above, controlled by getopts. # the way we output is based on args above, controlled by getopts.
if args['shortest']: # just show the question and answer. if args["shortest"]: # just show the question and answer.
# outputlist has pod titles, ordered by importance, not every input has a clear Input/Result (question/answer). # outputlist has pod titles, ordered by importance, not every input has a clear Input/Result (question/answer).
outputlist = [outputlist[item] for item in sorted(outputlist.keys())] outputlist = [outputlist[item] for item in sorted(outputlist.keys())]
question = output.get(outputlist[0]) # get first (question). question = output.get(outputlist[0]) # get first (question).
answer = output.get(outputlist[1]) # get second (answer). answer = output.get(outputlist[1]) # get second (answer).
# output time. display with color or not? # output time. display with color or not?
if self.registryValue('disableANSI'): if self.registryValue("disableANSI"):
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format("".join([i for i in question]), " | ".join([i for i in answer]))).replace(": | ", ": ")) irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(
"".join([i for i in question]),
" | ".join([i for i in answer]),
),
).replace(": | ", ": ")
)
else: # with ansi. else: # with ansi.
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format(self._bold("".join([i for i in question])), " | ".join([i for i in answer]))).replace(": | ", ": ")) irc.reply(
elif args['fulloutput']: # show everything. no limits. re.sub(
"\s+",
" ",
"{0} :: {1}".format(
self._bold("".join([i for i in question])),
" | ".join([i for i in answer]),
),
).replace(": | ", ": ")
)
elif args["fulloutput"]: # show everything. no limits.
# grab all values, sorted via the position number. output one per line. # grab all values, sorted via the position number. output one per line.
for (k, v) in sorted(outputlist.items()): for (k, v) in sorted(outputlist.items()):
itemout = output.get(v) # items out will be a list of items. itemout = output.get(v) # items out will be a list of items.
# now decide to output with ANSI or not. # now decide to output with ANSI or not.
if self.registryValue('disableANSI'): if self.registryValue("disableANSI"):
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format(v, " | ".join(itemout))).replace(": | ", ": ")) irc.reply(
re.sub(
"\s+", " ", "{0} :: {1}".format(v, " | ".join(itemout))
).replace(": | ", ": ")
)
else: # with ansi. else: # with ansi.
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format(self._red(v), " | ".join(itemout))).replace(": | ", ": ")) irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(self._red(v), " | ".join(itemout)),
).replace(": | ", ": ")
)
else: # regular output, dictated by --lines or maxoutput. else: # regular output, dictated by --lines or maxoutput.
for q, k in enumerate(sorted(outputlist.keys())): for q, k in enumerate(sorted(outputlist.keys())):
if q < args['maxoutput']: # if less than max. if q < args["maxoutput"]: # if less than max.
itemout = output.get(outputlist[k]) # have the key, get the value, use for output. itemout = output.get(
outputlist[k]
) # have the key, get the value, use for output.
if itemout: if itemout:
if self.registryValue('disableANSI'): # display w/o formatting. if self.registryValue("disableANSI"): # display w/o formatting.
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format(outputlist[k], " | ".join(itemout))).replace(": | ", ": ")) irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(
outputlist[k], " | ".join(itemout)
),
).replace(": | ", ": ")
)
else: # display w/formatting. else: # display w/formatting.
irc.reply(re.sub("\s+", " ", "{0} :: {1}".format(self._red(outputlist[k]), " | ".join(itemout))).replace(": | ", ": ")) irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(
self._red(outputlist[k]), " | ".join(itemout)
),
).replace(": | ", ": ")
)
wolframalpha = wrap(wolframalpha, [getopts({'num':('int'), wolframalpha = wrap(
'reinterpret':'', wolframalpha,
'usemetric':'', [
'shortest':'', getopts(
'fulloutput':''}), ('text')]) {
"num": "int",
"reinterpret": "",
"usemetric": "",
"shortest": "",
"fulloutput": "",
}
),
"text",
],
)
def btc(self, irc, msg, args, amount, currency): def btc(self, irc, msg, args, amount, currency):
"""<amount> <currency> """<amount> <currency>
@ -217,17 +299,22 @@ class WolframAlpha(callbacks.Plugin):
Convert bitcoin to another currency""" Convert bitcoin to another currency"""
# check for API key before we can do anything. # check for API key before we can do anything.
apiKey = self.registryValue('apiKey') apiKey = self.registryValue("apiKey")
if not apiKey or apiKey == "Not set": if not apiKey or apiKey == "Not set":
irc.reply("Wolfram Alpha API key not set. see 'config help supybot.plugins.WolframAlpha.apiKey'.") irc.reply(
"Wolfram Alpha API key not set. see 'config help"
" supybot.plugins.WolframAlpha.apiKey'."
)
return return
# first, url arguments, some of which getopts and config variables can manipulate. # first, url arguments, some of which getopts and config variables can manipulate.
urlArgs = { 'input':'%d btc to %s' % (amount,currency), urlArgs = {
'appid':apiKey, "input": "{:.2f} btc to {}".format(amount, currency),
'reinterpret':'false', "appid": apiKey,
'format':'plaintext', "reinterpret": "false",
'units':'nonmetric' } "format": "plaintext",
url = 'http://api.wolframalpha.com/v2/query?' + utils.web.urlencode(urlArgs) "units": "nonmetric",
}
url = "http://api.wolframalpha.com/v2/query?" + utils.web.urlencode(urlArgs)
print(url) print(url)
try: try:
page = utils.web.getUrl(url) page = utils.web.getUrl(url)
@ -244,19 +331,20 @@ class WolframAlpha(callbacks.Plugin):
return return
# each answer has a different amount of pods. # each answer has a different amount of pods.
for pod in document.findall('.//pod'): for pod in document.findall(".//pod"):
title = pod.attrib['title'] # title of it. title = pod.attrib["title"] # title of it.
if title == "Result": if title == "Result":
for plaintext in pod.findall('.//plaintext'): for plaintext in pod.findall(".//plaintext"):
print((plaintext.text)) print((plaintext.text))
if 'not compatible' in plaintext.text: if "not compatible" in plaintext.text:
irc.reply('Conversion from btc to %s not available' % currency) irc.reply("Conversion from btc to %s not available" % currency)
else: else:
converted_amount = plaintext.text.split('(')[0].strip() converted_amount = plaintext.text.split("(")[0].strip()
irc.reply('%s%d = %s' % ('\u0e3f', amount, converted_amount)) irc.reply("{}{:.2f} = {}".format("\u0e3f", amount, converted_amount))
btc = wrap(btc, ["float", "text"])
btc = wrap(btc,['float', 'text'])
Class = WolframAlpha Class = WolframAlpha

View File

@ -33,12 +33,13 @@ from supybot.test import *
class WolframAlphaTestCase(PluginTestCase): class WolframAlphaTestCase(PluginTestCase):
plugins = ('WolframAlpha',) plugins = ("WolframAlpha",)
def testWolframAlpha(self): def testWolframAlpha(self):
apiKey = os.environ.get('apiKey') apiKey = os.environ.get("apiKey")
conf.supybot.plugins.WolframAlpha.apiKey.setValue(apiKey) conf.supybot.plugins.WolframAlpha.apiKey.setValue(apiKey)
conf.supybot.plugins.WolframAlpha.disableANSI.setValue('True') conf.supybot.plugins.WolframAlpha.disableANSI.setValue("True")
self.assertResponse('wolframalpha --shortest 2+2', '2+2 :: 4') self.assertResponse("wolframalpha --shortest 2+2", "2+2 :: 4")
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: