Permalink
Browse files

Version 1.0 of Misty

  • Loading branch information...
0 parents commit c1a96d3b8621e35af380450a2c84133a992df22d @michael-ranieri michael-ranieri committed Jul 8, 2011
Showing with 573 additions and 0 deletions.
  1. +20 −0 LICENSE
  2. +61 −0 README.textile
  3. +37 −0 isles/core/calculate.py
  4. +86 −0 isles/core/search.py
  5. +71 −0 isles/core/web.py
  6. +11 −0 isles/examples/subprocess.py
  7. +44 −0 lighthouse.py
  8. +207 −0 misty_core.py
  9. +36 −0 settings.py
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (C) 2011 Michael Ranieri <michael.d.ranieri at gmail.com>
+
+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.
@@ -0,0 +1,61 @@
+h1. Overview:
+
+Misty is an asynchronous and multiprocessing IRC Bot that aims to be:
+* Easily extendable with user based modules called Isles.
+* Powerful message distribution resembling Django's URL dispatcher aka Lighthouse.
+* Quick, Friendly, and as non IRC Botty as possible.
+
+Misty was inspired by "Phenny":http://inamidst.com/phenny/,
+and "DjangoBot":https://code.djangoproject.com/wiki/DjangoBot.
+
+h1. Coming Soon:
+
+* Proper logging & searching via Django.
+* Join multiple channels.
+* More Core Isles.
+* Better Lighthouse control.
+
+h1. Requirements:
+
+* "Twisted":http://twistedmatrix.com/
+* "PostgreSQL":http://www.postgresql.org/
+* "pyPgSQL":http://pypgsql.sourceforge.net/
+* Misty has only been tested on OSX 10.6 and python 2.6
+
+h1. Installation:
+
+# Clone repo in desired directory.
+# Duplicate and rename Misty/settings.py to settings_local.py
+# Change Misty/settings_local.py to match your database & IRC server.
+# Type @python Misty/misty_core.py@ in cmd line to start Misty.
+
+h1. Extending:
+
+# Make a IsleName.py in the directory you set with PATH_TO_ISLES in Misty/settings.py
+# Make sure the IsleName.py has execute permission.
+# Make sure IsleName.py has @#!/usr/bin/env python@
+# Edit Misty/lighthouse.py to include your new Isle.
+# If Misty is running, reload the list of Isles with @{nickname}:reload@ in IRC
+
+h1. License
+
+Copyright (C) 2011 Michael Ranieri <michael.d.ranieri at gmail.com>
+
+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.
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+calc.py - Phenny Calculator Module
+Copyright 2008, Sean B. Palmer, inamidst.com
+Licensed under the Eiffel Forum License 2.
+
+http://inamidst.com/phenny/
+"""
+
+# MODIFIED FROM FILE ABOVE TO WORK WITH MISTY
+
+import sys, re, web
+
+def c(input):
+ """Google calculator."""
+ q = re.split(' ', input, 2)
+ q = q[1].encode('utf-8')
+ q = q.replace('\xcf\x95', 'phi') # utf-8 U+03D5
+ q = q.replace('\xcf\x80', 'pi') # utf-8 U+03C0
+ uri = 'http://www.google.com/ig/calculator?q='
+ bytes = web.get(uri + web.urllib.quote(q))
+ parts = bytes.split('",')
+ answer = [p for p in parts if p.startswith('rhs: "')][0][6:]
+ if answer:
+ answer = answer.decode('unicode-escape')
+ answer = answer.replace(u'\xc2\xa0', ',')
+ answer = answer.replace('<sup>', '^(')
+ answer = answer.replace('</sup>', ')')
+ answer = web.decode(answer)
+ print answer.encode('utf-8')
+ else: print 'Sorry, no result.'
+
+if __name__ == '__main__':
+ c(sys.argv[1])
+
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+"""
+search.py - Phenny Web Search Module
+Copyright 2008-9, Sean B. Palmer, inamidst.com
+Licensed under the Eiffel Forum License 2.
+
+http://inamidst.com/phenny/
+"""
+
+import re, web, sys
+
+def search(query):
+ """Search using AjaxSearch, and return its JSON."""
+ uri = 'http://ajax.googleapis.com/ajax/services/search/web'
+ args = '?v=1.0&safe=off&q=' + web.urllib.quote(query.encode('utf-8'))
+ bytes = web.get(uri + args)
+ return web.json(bytes)
+
+def result(query):
+ results = search(query)
+ try: return results['responseData']['results'][0]['unescapedUrl']
+ except IndexError: return None
+
+def count(query):
+ results = search(query)
+ if not results.has_key('responseData'): return '0'
+ if not results['responseData'].has_key('cursor'): return '0'
+ if not results['responseData']['cursor'].has_key('estimatedResultCount'):
+ return '0'
+ return results['responseData']['cursor']['estimatedResultCount']
+
+def formatnumber(n):
+ """Format a number with beautiful commas."""
+ parts = list(str(n))
+ for i in range((len(parts) - 3), 0, -3):
+ parts.insert(i, ',')
+ return ''.join(parts)
+
+def g(input):
+ """Queries Google for the specified input."""
+ query = re.split(' ', input, 2)[1]
+ uri = result(query)
+ if uri:
+ print uri
+ else: print "No results found for '%s'." % query
+g.commands = ['g']
+
+def gc(input):
+ """Returns the number of Google results for the specified input."""
+ query = re.split(' ', input, 2)[1]
+ num = formatnumber(count(query))
+ print query + ': ' + num
+gc.commands = ['gc']
+
+def gcs(input):
+ queries = r_query.findall(re.split(' ', input, 2)[1])
+ if len(queries) > 6:
+ print 'Sorry, can only compare up to six things.'
+ sys.exit(0)
+
+ results = []
+ for i, query in enumerate(queries):
+ query = query.strip('[]')
+ n = int((formatnumber(count(query)) or '0').replace(',', ''))
+ results.append((n, query))
+ if i >= 2: __import__('time').sleep(0.25)
+ if i >= 4: __import__('time').sleep(0.25)
+
+ results = [(term, n) for (n, term) in reversed(sorted(results))]
+ reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results)
+ print reply
+gcs.commands = ['gcs', 'comp']
+
+if __name__ == '__main__':
+
+ r_query = re.compile(
+ r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
+ )
+
+ command = re.split(' ', sys.argv[1])[0]
+ if command == '.g':
+ g(sys.argv[1])
+ elif command == '.gc':
+ gc(sys.argv[1])
+ elif command == '.gcs':
+ gcs(sys.argv[1])
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+"""
+web.py - Web Facilities
+Author: Sean B. Palmer, inamidst.com
+About: http://inamidst.com/phenny/
+"""
+
+import re, urllib
+from htmlentitydefs import name2codepoint
+
+class Grab(urllib.URLopener):
+ def __init__(self, *args):
+ self.version = 'Mozilla/5.0 (Phenny)'
+ urllib.URLopener.__init__(self, *args)
+ def http_error_default(self, url, fp, errcode, errmsg, headers):
+ return urllib.addinfourl(fp, [headers, errcode], "http:" + url)
+urllib._urlopener = Grab()
+
+def get(uri):
+ if not uri.startswith('http'):
+ return
+ u = urllib.urlopen(uri)
+ bytes = u.read()
+ u.close()
+ return bytes
+
+def head(uri):
+ if not uri.startswith('http'):
+ return
+ u = urllib.urlopen(uri)
+ info = u.info()
+ u.close()
+ return info
+
+def post(uri, query):
+ if not uri.startswith('http'):
+ return
+ data = urllib.urlencode(query)
+ u = urllib.urlopen(uri, data)
+ bytes = u.read()
+ u.close()
+ return bytes
+
+r_entity = re.compile(r'&([^;\s]+);')
+
+def entity(match):
+ value = match.group(1).lower()
+ if value.startswith('#x'):
+ return unichr(int(value[2:], 16))
+ elif value.startswith('#'):
+ return unichr(int(value[1:]))
+ elif name2codepoint.has_key(value):
+ return unichr(name2codepoint[value])
+ return '[' + value + ']'
+
+def decode(html):
+ return r_entity.sub(entity, html)
+
+r_string = re.compile(r'("(\\.|[^"\\])*")')
+r_json = re.compile(r'^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$')
+env = {'__builtins__': None, 'null': None, 'true': True, 'false': False}
+
+def json(text):
+ """Evaluate JSON text safely (we hope)."""
+ if r_json.match(r_string.sub('', text)):
+ text = r_string.sub(lambda m: 'u' + m.group(1), text)
+ return eval(text.strip(' \t\r\n'), env, {})
+ raise ValueError('Input must be serialised JSON.')
+
+if __name__=="__main__":
+ main()
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+# Copyright (C) 2011 Michael Ranieri <michael.d.ranieri at gmail.com>
+
+import time, sys
+
+print sys.argv[2] + " sent a message to channel " + sys.argv[3]
+
+time.sleep(10)
+
+print "Here is the message that was sent 10 seconds ago:"
+print sys.argv[1]
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# Copyright (C) 2011 Michael Ranieri <michael.d.ranieri at gmail.com>
+
+# input to methods will be a tuple (Message from irc, user who sent msg, channel)
+# Must return a tuple (bool, string, string)
+# bool is whether or not you want to send this particular message to your Isle for processing
+# The first string is the relative location of the isle from the settings.PATH_TO_ISLE
+# The second string is the filename of the isle.
+
+# NOTE: some messages beginning with {nickname}: are reserved for internal functions
+# Try to avoid using {nickname}: so you don't have any name conflicts with internal functions
+
+import re
+
+# Calculates user input
+def calculate(msg, user, channel):
+ if msg.startswith('.c'):
+ return (True, "core/calculate.py", "calculate.py")
+ else:
+ return (False, None, None)
+
+# Searches google and returns top hit
+def search(msg, user, channel):
+ if msg.startswith('.g') \
+ or msg.startswith('.gc') \
+ or msg.startswith('.gcs'):
+ return (True, "core/search.py", "search.py")
+ else:
+ return (False, None, None)
+
+# Example Isles
+
+# Echo a message 10 seconds later
+def subprocess(msg, user, channel):
+ if re.search('example', msg) != None:
+ return (True, "examples/subprocess.py", "subprocess.py")
+ else:
+ return(False, None, None)
+
+isles = [
+ calculate,
+ search,
+ subprocess,
+]
Oops, something went wrong.

0 comments on commit c1a96d3

Please sign in to comment.