forked from oddluck

This commit is contained in:
Pratyush Desai 2021-09-10 11:27:52 +05:30
commit a94e1d8fa3
Signed by: pratyush
GPG Key ID: DBA5BB7505946FAD
6 changed files with 661 additions and 0 deletions

20
LICENSE.txt Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 spline
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

83
README.md Normal file
View File

@ -0,0 +1,83 @@
Retrieve info from WolframAlpha based on your queries.
Forked from https://github.com/ormanya/Supyiel/tree/master/WolframAlpha
# Limnoria plugin for WolframAlpha
## Introduction
There are at least 3 plugins floating around for WA. One of the big differences with each variant from users
is the differences in output due to the verbosity from how WA answers questions. Some answers can be
10+ lines and easily flood a channel, either having the bot flood off or getting it banned from a channel.
WA's API also has some input options that can be handy, along with some verbose "error" messages that can help
the user, which the other plugins do not utilize. I wanted to use the getopts power and make some configuration
options to display the data in a more friendly manner.
## Install
You will need a working Limnoria bot on Python 2.7 for this to work.
Go into your Limnoria plugin dir, usually ~/supybot/plugins and run:
```
git clone https://github.com/reticulatingspline/WolframAlpha
```
To install additional requirements, run:
```
pip install -r requirements.txt
```
or if you don't have or don't want to use root,
```
pip install -r requirements.txt --user
```
Next, load the plugin:
```
/msg bot load WolframAlpha
```
[Fetch an API key for WA](http://products.wolframalpha.com/developers/) by signing up (free).
Once getting this key, you will need to set it on your bot before things will work.
Reload once you perform this operation to start using it.
```
/msg bot config plugins.WolframAlpha.apiKey APIKEY
```
Now, reload the bot and you should be good to go:
```
/msg bot reload WolframAlpha
```
Optional: There are some config variables that can be set for the bot. They mainly control output stuff.
```
/msg bot config search WolframAlpha
```
## Example Usage
```
<spline> @wolframalpha 2+2
<myybot> Input :: 2+2
<myybot> Result :: 4
<myybot> Number name :: four
<myybot> Manipulatives illustration :: | + | | = | 2 | | 2 | | 4
<spline> @wolframalpha --shortest 2+2
<myybot> 2+2 :: 4
```
## About
All of my plugins are free and open source. When I first started out, one of the main reasons I was
able to learn was due to other code out there. If you find a bug or would like an improvement, feel
free to give me a message on IRC or fork and submit a pull request. Many hours do go into each plugin,
so, if you're feeling generous, I do accept donations via Amazon or browse my [wish list](http://amzn.com/w/380JKXY7P5IKE).
I'm always looking for work, so if you are in need of a custom feature, plugin or something bigger, contact me via GitHub or IRC.

75
__init__.py Normal file
View File

@ -0,0 +1,75 @@
###
# Copyright (c) 2014, spline
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
WolframAlpha: retrieve info from the Wolfram Alpha API
"""
import supybot
import supybot.world as world
import importlib
# 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.
__version__ = "2020.06.23+git"
# XXX Replace this with an appropriate author or supybot.Author instance.
__author__ = supybot.Author("reticulatingspline", "spline", "")
__maintainer__ = getattr(
supybot.authors,
"oddluck",
supybot.Author("oddluck", "oddluck", "oddluck@riseup.net"),
)
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
# This is a url where the most recent plugin package can be downloaded.
__url__ = "https://github.com/oddluck/limnoria-plugins/"
from . import config
from . import plugin
from imp import reload
# In case we're being reloaded.
importlib.reload(config)
importlib.reload(plugin)
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

84
config.py Normal file
View File

@ -0,0 +1,84 @@
###
# Copyright (c) 2014, spline
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization("WolframAlpha")
except:
# Placeholder that allows to run the plugin on a bot
# without the i18n module
_ = lambda x: x
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified himself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin("WolframAlpha", True)
WolframAlpha = conf.registerPlugin("WolframAlpha")
conf.registerGlobalValue(
WolframAlpha,
"apiKey",
registry.String("", """Your Wolfram Alpha API key.""", private=True),
)
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:

354
plugin.py Normal file
View File

@ -0,0 +1,354 @@
###
# Copyright (c) 2014, spline
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
# my libs
from collections import defaultdict
try:
import xml.etree.cElementTree as ElementTree
except ImportError:
import xml.etree.ElementTree as ElementTree
# supybot libs
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import re
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization("WolframAlpha")
except:
# Placeholder that allows to run the plugin on a bot
# without the i18n module
_ = lambda x: x
class WolframAlpha(callbacks.Plugin):
"""Add the help for "@plugin help WolframAlpha" here
This should describe *how* to use this plugin."""
threaded = True
######################
# INTERNAL FUNCTIONS #
######################
def _red(self, s):
return ircutils.mircColor(s, "red")
def _bold(self, s):
return ircutils.bold(s)
####################
# PUBLIC FUNCTIONS #
####################
# API Documentation. http://products.wolframalpha.com/api/documentation.html
def wolframalpha(self, irc, msg, args, optlist, optinput):
"""[--num #|--reinterpret|--usemetric|--shortest|--fulloutput] <query>
Returns answer from Wolfram Alpha API.
Use --num number to display a specific amount of lines.
Use --reinterpret to have WA logic to interpret question if not understood.
Use --usemetric to not display in imperial units.
Use --shortest for the shortest output (ignores lines).
Use --fulloutput to display everything from the API (can flood).
"""
# check for API key before we can do anything.
apiKey = self.registryValue("apiKey")
if not apiKey or apiKey == "Not set":
irc.reply(
"Wolfram Alpha API key not set. see 'config help"
" supybot.plugins.WolframAlpha.apiKey'."
)
return
# first, url arguments, some of which getopts and config variables can manipulate.
urlArgs = {
"input": optinput,
"appid": apiKey,
"reinterpret": "false",
"format": "plaintext",
"units": "nonmetric",
}
# check for config variables to manipulate URL arguments.
if not self.registryValue("useImperial"):
urlArgs["units"] = "metric"
if self.registryValue("reinterpretInput"):
urlArgs["reinterpret"] = "true"
# now handle input. default input arguments.
args = {
"maxoutput": self.registryValue("maxOutput"),
"shortest": None,
"fulloutput": None,
}
# handle getopts (optlist)
if optlist:
for (key, value) in optlist:
if key == "shortest":
args["shortest"] = True
if key == "fulloutput":
args["fulloutput"] = True
if key == "num":
args["maxoutput"] = value
if key == "usemetric":
urlArgs["units"] = "metric"
if key == "reinterpret":
urlArgs["reinterpret"] = "true"
# build url and query.
url = "http://api.wolframalpha.com/v2/query?" + utils.web.urlencode(urlArgs)
try:
page = utils.web.getUrl(url)
except Exception as e:
self.log.error("ERROR opening {0} message: {1}".format(url, e))
irc.reply("ERROR: Failed to open WolframAlpha API: {0}".format(url))
return
# now try to process XML.
try:
document = ElementTree.fromstring(page)
except Exception as e:
self.log.error("ERROR: Broke processing XML: {0}".format(e))
irc.reply("ERROR: Something broke processing XML from WolframAlpha's API.")
return
# document = ElementTree.fromstring(page) #.decode('utf-8'))
# 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":
errormsgs = []
for error in document.findall(".//error"):
errorcode = error.find("code").text
errormsg = error.find("msg").text
errormsgs.append("{0} - {1}".format(errorcode, errormsg))
# log and report to irc if we have these.
self.log.debug(
"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
# check if we have no success but also no error. (Did you mean?)
elif (
document.attrib["success"] == "false"
and document.attrib["error"] == "false"
):
errormsgs = [] # list to contain whatever is there.
for error in document.findall(".//futuretopic"):
errormsg = error.attrib["msg"]
errormsgs.append("FUTURE TOPIC: {0}".format(errormsg))
for error in document.findall(".//didyoumeans"):
errormsg = error.find("didyoumean").text
errormsgs.append("Did you mean? {0}".format(errormsg))
for error in document.findall(".//tips"):
errormsg = error.find("tip").attrib["text"].text
errormsgs.append("TIPS: {0}".format(errormsg))
# now output the messages to irc and log.
self.log.debug(
"ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)
)
irc.reply(
"ERROR with input: {0} API returned: {1}".format(optinput, errormsgs)
)
return
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.
# outputlist is used in sorting since defaultdict does not remember order/position.
output = defaultdict(list)
outputlist = {}
# each answer has a different amount of pods.
for pod in document.findall(".//pod"):
title = pod.attrib["title"] # title of it.
position = int(pod.attrib["position"]) # store pods int when we sort.
outputlist[position] = title # pu
for plaintext in pod.findall(".//plaintext"):
if plaintext.text:
output[title].append(plaintext.text.replace("\n", " "))
# last sanity check...
if len(output) == 0:
irc.reply("ERROR: I received no output looking up: {0}".format(optinput))
return
# done processing the XML so lets work on the output.
# the way we output is based on args above, controlled by getopts.
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 = [outputlist[item] for item in sorted(outputlist.keys())]
question = output.get(outputlist[0]) # get first (question).
answer = output.get(outputlist[1]) # get second (answer).
# output time. display with color or not?
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(": | ", ": ")
)
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(": | ", ": ")
)
elif args["fulloutput"]: # show everything. no limits.
# grab all values, sorted via the position number. output one per line.
for (k, v) in sorted(outputlist.items()):
itemout = output.get(v) # items out will be a list of items.
# now decide to output with ANSI or not.
if self.registryValue("disableANSI"):
irc.reply(
re.sub(
"\s+", " ", "{0} :: {1}".format(v, " | ".join(itemout))
).replace(": | ", ": ")
)
else: # with ansi.
irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(self._red(v), " | ".join(itemout)),
).replace(": | ", ": ")
)
else: # regular output, dictated by --lines or maxoutput.
for q, k in enumerate(sorted(outputlist.keys())):
if q < args["maxoutput"]: # if less than max.
itemout = output.get(
outputlist[k]
) # have the key, get the value, use for output.
if itemout:
if self.registryValue("disableANSI"): # display w/o formatting.
irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(
outputlist[k], " | ".join(itemout)
),
).replace(": | ", ": ")
)
else: # display w/formatting.
irc.reply(
re.sub(
"\s+",
" ",
"{0} :: {1}".format(
self._red(outputlist[k]), " | ".join(itemout)
),
).replace(": | ", ": ")
)
wolframalpha = wrap(
wolframalpha,
[
getopts(
{
"num": "int",
"reinterpret": "",
"usemetric": "",
"shortest": "",
"fulloutput": "",
}
),
"text",
],
)
def btc(self, irc, msg, args, amount, currency):
"""<amount> <currency>
Convert bitcoin to another currency"""
# check for API key before we can do anything.
apiKey = self.registryValue("apiKey")
if not apiKey or apiKey == "Not set":
irc.reply(
"Wolfram Alpha API key not set. see 'config help"
" supybot.plugins.WolframAlpha.apiKey'."
)
return
# first, url arguments, some of which getopts and config variables can manipulate.
urlArgs = {
"input": "{:.2f} btc to {}".format(amount, currency),
"appid": apiKey,
"reinterpret": "false",
"format": "plaintext",
"units": "nonmetric",
}
url = "http://api.wolframalpha.com/v2/query?" + utils.web.urlencode(urlArgs)
print(url)
try:
page = utils.web.getUrl(url)
except Exception as e:
self.log.error("ERROR opening {0} message: {1}".format(url, e))
irc.reply("ERROR: Failed to open WolframAlpha API: {0}".format(url))
return
# now try to process XML.
try:
document = ElementTree.fromstring(page)
except Exception as e:
self.log.error("ERROR: Broke processing XML: {0}".format(e))
irc.reply("ERROR: Something broke processing XML from WolframAlpha's API.")
return
# each answer has a different amount of pods.
for pod in document.findall(".//pod"):
title = pod.attrib["title"] # title of it.
if title == "Result":
for plaintext in pod.findall(".//plaintext"):
print((plaintext.text))
if "not compatible" in plaintext.text:
irc.reply("Conversion from btc to %s not available" % currency)
else:
converted_amount = plaintext.text.split("(")[0].strip()
irc.reply(
"{}{:.2f} = {}".format("\u0e3f", amount, converted_amount)
)
btc = wrap(btc, ["float", "text"])
Class = WolframAlpha
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=250:

45
test.py Normal file
View File

@ -0,0 +1,45 @@
###
# Copyright (c) 2014, spline
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import os
from supybot.test import *
class WolframAlphaTestCase(PluginTestCase):
plugins = ("WolframAlpha",)
def testWolframAlpha(self):
apiKey = os.environ.get("apiKey")
conf.supybot.plugins.WolframAlpha.apiKey.setValue(apiKey)
conf.supybot.plugins.WolframAlpha.disableANSI.setValue("True")
self.assertResponse("wolframalpha --shortest 2+2", "2+2 :: 4")
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: