summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile25
-rwxr-xr-xbot.py2
-rwxr-xr-xirc.py14
-rwxr-xr-xmodules/admin.py4
-rwxr-xr-xmodules/clock.py1
-rwxr-xr-xmodules/etymology.py31
-rwxr-xr-xmodules/oblique.py4
-rwxr-xr-xmodules/remind.py57
-rwxr-xr-xmodules/search.py77
-rwxr-xr-xmodules/seen.py25
-rwxr-xr-xmodules/startup.py45
-rwxr-xr-xmodules/tell.py8
-rwxr-xr-xmodules/translate.py121
-rwxr-xr-xmodules/twitter.py93
-rwxr-xr-xphenny22
-rwxr-xr-xproject26
16 files changed, 457 insertions, 98 deletions
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 6ea3aa9..0000000
--- a/Makefile
+++ /dev/null
@@ -1,25 +0,0 @@
-# Makefile
-# Copyright 2008, Sean B. Palmer, inamidst.com
-# Licensed under the Eiffel Forum License 2.
-
-# archive - Create phenny.tar.bz2 using git archive
-archive: ;
- # hg archive -t tbz2 phenny-hg.tar.bz2
- git archive --format=tar --prefix=phenny/ HEAD | bzip2 > phenny.tar.bz2
-
-# ci - Check the code into git and push to github
-ci: ;
- # hg ci
- git commit -a && git push origin master
-
-# log - Show a log of recent updates
-log: ;
- # git log --date=short --format='%h %ad %s'
- git graph
-
-# sync - Push phenny to pubble:opt/phenny/
-sync: ;
- rsync -avz ./ pubble:opt/phenny/
-
-help: ;
- @egrep '^# [a-z]+ - ' Makefile | sed 's/# //'
diff --git a/bot.py b/bot.py
index 453dbc4..e45c035 100755
--- a/bot.py
+++ b/bot.py
@@ -205,7 +205,7 @@ class Phenny(irc.Bot):
items = self.commands[priority].items()
for regexp, funcs in items:
for func in funcs:
- if event != func.event: continue
+ if event != func.event and func.event != '*': continue
match = regexp.match(text)
if match:
diff --git a/irc.py b/irc.py
index e6008e0..251ed64 100755
--- a/irc.py
+++ b/irc.py
@@ -42,15 +42,21 @@ class Bot(asynchat.async_chat):
import threading
self.sending = threading.RLock()
+ def initiate_send(self):
+ self.sending.acquire()
+ asynchat.async_chat.initiate_send(self)
+ self.sending.release()
+
# def push(self, *args, **kargs):
# asynchat.async_chat.push(self, *args, **kargs)
def __write(self, args, text=None):
- # print '%r %r %r' % (self, args, text)
+ # print 'PUSH: %r %r %r' % (self, args, text)
try:
if text is not None:
- self.push((' '.join(args) + ' :' + text)[:512] + '\r\n')
- else: self.push(' '.join(args)[:512] + '\r\n')
+ # 510 because CR and LF count too, as nyuszika7h points out
+ self.push((' '.join(args) + ' :' + text)[:510] + '\r\n')
+ else: self.push(' '.join(args)[:510] + '\r\n')
except IndexError:
pass
@@ -101,7 +107,7 @@ class Bot(asynchat.async_chat):
line = line[:-1]
self.buffer = ''
- # print line
+ # print 'GOT:', repr(line)
if line.startswith(':'):
source, line = line[1:].split(' ', 1)
else: source = None
diff --git a/modules/admin.py b/modules/admin.py
index 249f117..b42822a 100755
--- a/modules/admin.py
+++ b/modules/admin.py
@@ -55,8 +55,8 @@ def me(phenny, input):
if input.sender.startswith('#'): return
if input.admin:
msg = '\x01ACTION %s\x01' % input.group(3)
- phenny.msg(input.group(2), msg)
-me.rule = (['me'], r'(#?\S+) (.*)')
+ phenny.msg(input.group(2) or input.sender, msg)
+me.rule = (['me'], r'(#?\S+) (.+)')
me.priority = 'low'
if __name__ == '__main__':
diff --git a/modules/clock.py b/modules/clock.py
index f848423..91f2d5b 100755
--- a/modules/clock.py
+++ b/modules/clock.py
@@ -280,6 +280,7 @@ tock.priority = 'high'
def npl(phenny, input):
"""Shows the time from NPL's SNTP server."""
+ # for server in ('ntp1.npl.co.uk', 'ntp2.npl.co.uk'):
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto('\x1b' + 47 * '\0', ('ntp1.npl.co.uk', 123))
data, address = client.recvfrom(1024)
diff --git a/modules/etymology.py b/modules/etymology.py
index 55c5deb..cc93cfe 100755
--- a/modules/etymology.py
+++ b/modules/etymology.py
@@ -7,17 +7,25 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/
"""
-import re
+import re, urllib
import web
from tools import deprecated
-etyuri = 'http://etymonline.com/?term=%s'
-etysearch = 'http://etymonline.com/?search=%s'
+etysite = 'http://www.etymonline.com/index.php?'
+etyuri = etysite + 'allowed_in_frame=0&term=%s'
+etysearch = etysite + 'allowed_in_frame=0&search=%s'
r_definition = re.compile(r'(?ims)<dd[^>]*>.*?</dd>')
r_tag = re.compile(r'<(?!!)[^>]+>')
r_whitespace = re.compile(r'[\t\r\n ]+')
+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)
+
abbrs = [
'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp',
'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk',
@@ -46,7 +54,11 @@ def etymology(word):
raise ValueError("Word too long: %s[...]" % word[:10])
word = {'axe': 'ax/axe'}.get(word, word)
+ grab = urllib._urlopener
+ urllib._urlopener = Grab()
+ urllib._urlopener.addheader("Referer", "http://www.etymonline.com/")
bytes = web.get(etyuri % web.urllib.quote(word))
+ urllib._urlopener = grab
definitions = r_definition.findall(bytes)
if not definitions:
@@ -58,10 +70,11 @@ def etymology(word):
return None
sentence = m.group(0)
- try:
- sentence = unicode(sentence, 'iso-8859-1')
- sentence = sentence.encode('utf-8')
- except: pass
+ # try:
+ # sentence = unicode(sentence, 'iso-8859-1')
+ # sentence = sentence.encode('utf-8')
+ # except: pass
+ sentence = web.decode(sentence)
maxlength = 275
if len(sentence) > maxlength:
@@ -71,7 +84,7 @@ def etymology(word):
sentence = ' '.join(words) + ' [...]'
sentence = '"' + sentence.replace('"', "'") + '"'
- return sentence + ' - ' + (etyuri % word)
+ return sentence + ' - ' + ('http://etymonline.com/index.php?term=%s' % web.urllib.quote(word))
@deprecated
def f_etymology(self, origin, match, args):
@@ -89,7 +102,7 @@ def f_etymology(self, origin, match, args):
self.msg(origin.sender, result)
else:
uri = etysearch % word
- msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
+ msg = 'Can\'t find the etymology for "%s". Try %s' % (word, ('http://etymonline.com/index.php?term=%s' % web.urllib.quote(word)))
self.msg(origin.sender, msg)
# @@ Cf. http://swhack.com/logs/2006-01-04#T01-50-22
f_etymology.rule = (['ety'], r"(.+?)$")
diff --git a/modules/oblique.py b/modules/oblique.py
index 7bd6718..d93446e 100755
--- a/modules/oblique.py
+++ b/modules/oblique.py
@@ -43,7 +43,9 @@ def service(phenny, input, command, args):
lines = bytes.splitlines()
if not lines:
return phenny.reply("Sorry, the service didn't respond any output.")
- phenny.say(lines[0][:350])
+ try: line = lines[0].encode('utf-8')[:350]
+ except: line = lines[0][:250]
+ phenny.say(line)
def refresh(phenny):
if hasattr(phenny.config, 'services'):
diff --git a/modules/remind.py b/modules/remind.py
index ec1a4d1..fbfd258 100755
--- a/modules/remind.py
+++ b/modules/remind.py
@@ -36,7 +36,10 @@ def dump_database(name, data):
def setup(phenny):
phenny.rfn = filename(phenny)
+
+ # phenny.sending.acquire()
phenny.rdb = load_database(phenny.rfn)
+ # phenny.sending.release()
def monitor(phenny):
time.sleep(5)
@@ -51,7 +54,10 @@ def setup(phenny):
phenny.msg(channel, nick + ': ' + message)
else: phenny.msg(channel, nick + '!')
del phenny.rdb[oldtime]
+
+ # phenny.sending.acquire()
dump_database(phenny.rfn, phenny.rdb)
+ # phenny.sending.release()
time.sleep(2.5)
targs = (phenny,)
@@ -132,5 +138,56 @@ def remind(phenny, input):
else: phenny.reply('Okay, will remind in %s secs' % duration)
remind.commands = ['in']
+r_time = re.compile(r'^([0-9]{2}[:.][0-9]{2})')
+r_zone = re.compile(r'( ?([A-Za-z]+|[+-]\d\d?))')
+
+import calendar
+from clock import TimeZones
+
+def at(phenny, input):
+ bytes = input[4:]
+
+ m = r_time.match(bytes)
+ if not m:
+ return phenny.reply("Sorry, didn't understand the time spec.")
+ t = m.group(1).replace('.', ':')
+ bytes = bytes[len(t):]
+
+ m = r_zone.match(bytes)
+ if not m:
+ return phenny.reply("Sorry, didn't understand the zone spec.")
+ z = m.group(2)
+ bytes = bytes[len(m.group(1)):].strip().encode("utf-8")
+
+ if z.startswith('+') or z.startswith('-'):
+ tz = int(z)
+
+ if TimeZones.has_key(z):
+ tz = TimeZones[z]
+ else: return phenny.reply("Sorry, didn't understand the time zone.")
+
+ d = time.strftime("%Y-%m-%d", time.gmtime())
+ d = time.strptime(("%s %s" % (d, t)).encode("utf-8"), "%Y-%m-%d %H:%M")
+
+ d = int(calendar.timegm(d) - (3600 * tz))
+ duration = int((d - time.time()) / 60)
+
+ if duration < 1:
+ return phenny.reply("Sorry, that date is this minute or in the past. And only times in the same day are supported!")
+
+ # phenny.say("%s %s %s" % (t, tz, d))
+
+ reminder = (input.sender, input.nick, bytes)
+ # phenny.say(str((d, reminder)))
+ try: phenny.rdb[d].append(reminder)
+ except KeyError: phenny.rdb[d] = [reminder]
+
+ phenny.sending.acquire()
+ dump_database(phenny.rfn, phenny.rdb)
+ phenny.sending.release()
+
+ phenny.reply("Reminding at %s %s - in %s minute(s)" % (t, z, duration))
+at.commands = ['at']
+
if __name__ == '__main__':
print __doc__.strip()
diff --git a/modules/search.py b/modules/search.py
index bfc50bd..5d038af 100755
--- a/modules/search.py
+++ b/modules/search.py
@@ -20,6 +20,8 @@ class Grab(web.urllib.URLopener):
def google_ajax(query):
"""Search using AjaxSearch, and return its JSON."""
+ if isinstance(query, unicode):
+ query = query.encode('utf-8')
uri = 'http://ajax.googleapis.com/ajax/services/search/web'
args = '?v=1.0&safe=off&q=' + web.urllib.quote(query)
handler = web.urllib._urlopener
@@ -51,6 +53,9 @@ def formatnumber(n):
parts.insert(i, ',')
return ''.join(parts)
+def old_gc(query):
+ return formatnumber(google_count(query))
+
def g(phenny, input):
"""Queries Google for the specified input."""
query = input.group(2)
@@ -69,7 +74,7 @@ g.commands = ['g']
g.priority = 'high'
g.example = '.g swhack'
-def gc(phenny, input):
+def oldgc(phenny, input):
"""Returns the number of Google results for the specified input."""
query = input.group(2)
if not query:
@@ -77,9 +82,8 @@ def gc(phenny, input):
query = query.encode('utf-8')
num = formatnumber(google_count(query))
phenny.say(query + ': ' + num)
-gc.commands = ['gc']
-gc.priority = 'high'
-gc.example = '.gc extrapolate'
+oldgc.commands = ['ogc', 'oldgc']
+oldgc.example = '.oldgc extrapolate'
r_query = re.compile(
r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
@@ -112,8 +116,9 @@ def bing_search(query, lang='en-GB'):
query = web.urllib.quote(query)
base = 'http://www.bing.com/search?mkt=%s&q=' % lang
bytes = web.get(base + query)
- m = r_bing.search(bytes)
- if m: return m.group(1)
+ for result in r_bing.findall(bytes):
+ if "r.msn.com/" in result: continue
+ return result
def bing(phenny, input):
"""Queries Bing for the specified input."""
@@ -196,5 +201,65 @@ def suggest(phenny, input):
else: phenny.reply('Sorry, no result.')
suggest.commands = ['suggest']
+def new_gc(query):
+ uri = 'https://www.google.com/search?hl=en&q='
+ uri = uri + web.urllib.quote(query).replace('+', '%2B')
+ # if '"' in query: uri += '&tbs=li:1'
+ bytes = web.get(uri)
+ if "did not match any documents" in bytes:
+ return "0"
+ for result in re.compile(r'(?ims)([0-9,]+) results?').findall(bytes):
+ return result
+ return None
+
+def newest_gc(query):
+ uri = 'https://www.google.com/search?hl=en&q='
+ uri = uri + web.urllib.quote(query).replace('+', '%2B')
+ bytes = web.get(uri + '&tbs=li:1')
+ if "did not match any documents" in bytes:
+ return "0"
+ for result in re.compile(r'(?ims)([0-9,]+) results?').findall(bytes):
+ return result
+ return None
+
+def newerest_gc(query):
+ uri = 'https://www.google.com/search?hl=en&q='
+ uri = uri + web.urllib.quote(query).replace('+', '%2B')
+ bytes = web.get(uri + '&prmd=imvns&start=950')
+ if "did not match any documents" in bytes:
+ return "0"
+ for result in re.compile(r'(?ims)([0-9,]+) results?').findall(bytes):
+ return result
+ return None
+
+def ngc(phenny, input):
+ if not input.group(2):
+ return phenny.reply("No query term.")
+ query = input.group(2).encode('utf-8')
+ result = new_gc(query)
+ if result:
+ phenny.say(query + ": " + result)
+ else: phenny.reply("Sorry, couldn't get a result.")
+
+ngc.commands = ['ngc']
+ngc.priority = 'high'
+ngc.example = '.ngc extrapolate'
+
+def gc(phenny, input):
+ if not input.group(2):
+ return phenny.reply("No query term.")
+ query = input.group(2).encode('utf-8')
+ result = query + ": "
+ result += (old_gc(query) or "?") + " (api)"
+ result += ", " + (newerest_gc(query) or "?") + " (end)"
+ result += ", " + (new_gc(query) or "?") + " (site)"
+ if '"' in query:
+ result += ", " + (newest_gc(query) or "?") + " (verbatim)"
+ phenny.say(result)
+
+gc.commands = ['gc']
+gc.priority = 'high'
+gc.example = '.gc extrapolate'
+
if __name__ == '__main__':
print __doc__.strip()
diff --git a/modules/seen.py b/modules/seen.py
index 26dc05f..8ed41a8 100755
--- a/modules/seen.py
+++ b/modules/seen.py
@@ -10,21 +10,24 @@ http://inamidst.com/phenny/
import time
from tools import deprecated
-@deprecated
-def f_seen(self, origin, match, args):
+def seen(phenny, input):
""".seen <nick> - Reports when <nick> was last seen."""
- if origin.sender == '#talis': return
- nick = match.group(2).lower()
- if not hasattr(self, 'seen'):
- return self.msg(origin.sender, '?')
- if self.seen.has_key(nick):
- channel, t = self.seen[nick]
+ nick = input.group(2)
+ if not nick:
+ return phenny.reply("Need a nickname to search for...")
+ nick = nick.lower()
+
+ if not hasattr(phenny, 'seen'):
+ return phenny.reply("?")
+
+ if phenny.seen.has_key(nick):
+ channel, t = phenny.seen[nick]
t = time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime(t))
msg = "I last saw %s at %s on %s" % (nick, t, channel)
- self.msg(origin.sender, str(origin.nick) + ': ' + msg)
- else: self.msg(origin.sender, "Sorry, I haven't seen %s around." % nick)
-f_seen.rule = (['seen'], r'(\S+)')
+ phenny.reply(msg)
+ else: phenny.reply("Sorry, I haven't seen %s around." % nick)
+seen.rule = (['seen'], r'(\S+)')
@deprecated
def f_note(self, origin, match, args):
diff --git a/modules/startup.py b/modules/startup.py
index 6fc7fae..81b3ecf 100755
--- a/modules/startup.py
+++ b/modules/startup.py
@@ -7,17 +7,60 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/
"""
+import threading, time
+
+def setup(phenny):
+ print("Setting up phenny")
+ # by clsn
+ phenny.data = {}
+ refresh_delay = 300.0
+
+ if hasattr(phenny.config, 'refresh_delay'):
+ try: refresh_delay = float(phenny.config.refresh_delay)
+ except: pass
+
+ def close():
+ print "Nobody PONGed our PING, restarting"
+ phenny.handle_close()
+
+ def pingloop():
+ timer = threading.Timer(refresh_delay, close, ())
+ phenny.data['startup.setup.timer'] = timer
+ phenny.data['startup.setup.timer'].start()
+ # print "PING!"
+ phenny.write(('PING', phenny.config.host))
+ phenny.data['startup.setup.pingloop'] = pingloop
+
+ def pong(phenny, input):
+ try:
+ # print "PONG!"
+ phenny.data['startup.setup.timer'].cancel()
+ time.sleep(refresh_delay + 60.0)
+ pingloop()
+ except: pass
+ pong.event = 'PONG'
+ pong.thread = True
+ pong.rule = r'.*'
+ phenny.variables['pong'] = pong
+
def startup(phenny, input):
+ import time
+
+ # Start the ping loop. Has to be done after USER on e.g. quakenet
+ if phenny.data.get('startup.setup.pingloop'):
+ phenny.data['startup.setup.pingloop']()
+
if hasattr(phenny.config, 'serverpass'):
phenny.write(('PASS', phenny.config.serverpass))
if hasattr(phenny.config, 'password'):
phenny.msg('NickServ', 'IDENTIFY %s' % phenny.config.password)
- __import__('time').sleep(5)
+ time.sleep(5)
# Cf. http://swhack.com/logs/2005-12-05#T19-32-36
for channel in phenny.channels:
phenny.write(('JOIN', channel))
+ time.sleep(0.5)
startup.rule = r'(.*)'
startup.event = '251'
startup.priority = 'low'
diff --git a/modules/tell.py b/modules/tell.py
index d3ee609..a82fad1 100755
--- a/modules/tell.py
+++ b/modules/tell.py
@@ -131,9 +131,13 @@ def message(phenny, input):
for remkey in remkeys:
if not remkey.endswith('*') or remkey.endswith(':'):
if tellee.lower() == remkey:
+ phenny.sending.acquire()
reminders.extend(getReminders(phenny, channel, remkey, tellee))
- elif tellee.lower().startswith(remkey.rstrip('*:')):
+ phenny.sending.release()
+ elif tellee.lower().strip("0123456789_-[]`") == remkey.rstrip('*:'):
+ phenny.sending.acquire()
reminders.extend(getReminders(phenny, channel, remkey, tellee))
+ phenny.sending.release()
for line in reminders[:maximum]:
phenny.say(line)
@@ -144,7 +148,9 @@ def message(phenny, input):
phenny.msg(tellee, line)
if len(phenny.reminders.keys()) != remkeys:
+ phenny.sending.acquire()
dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell
+ phenny.sending.release()
message.rule = r'(.*)'
message.priority = 'low'
diff --git a/modules/translate.py b/modules/translate.py
index d5ae41f..11e4f28 100755
--- a/modules/translate.py
+++ b/modules/translate.py
@@ -11,22 +11,38 @@ http://inamidst.com/phenny/
import re, urllib
import web
-def detect(text):
- uri = 'http://ajax.googleapis.com/ajax/services/language/detect'
- q = urllib.quote(text)
- bytes = web.get(uri + '?q=' + q + '&v=1.0')
- result = web.json(bytes)
- try: return result['responseData']['language']
- except Exception: return None
-
-def translate(text, input, output):
- uri = 'http://ajax.googleapis.com/ajax/services/language/translate'
- q = urllib.quote(text)
- pair = input + '%7C' + output
- bytes = web.get(uri + '?q=' + q + '&v=1.0&langpair=' + pair)
- result = web.json(bytes)
- try: return result['responseData']['translatedText'].encode('cp1252')
- except Exception: return None
+def translate(text, input='auto', output='en'):
+ raw = False
+ if output.endswith('-raw'):
+ output = output[:-4]
+ raw = True
+
+ import urllib2, json
+ opener = urllib2.build_opener()
+ opener.addheaders = [(
+ 'User-Agent', 'Mozilla/5.0' +
+ '(X11; U; Linux i686)' +
+ 'Gecko/20071127 Firefox/2.0.0.11'
+ )]
+
+ input, output = urllib.quote(input), urllib.quote(output)
+ text = urllib.quote(text)
+
+ result = opener.open('http://translate.google.com/translate_a/t?' +
+ ('client=t&hl=en&sl=%s&tl=%s&multires=1' % (input, output)) +
+ ('&otf=1&ssel=0&tsel=0&uptl=en&sc=1&text=%s' % text)).read()
+
+ while ',,' in result:
+ result = result.replace(',,', ',null,')
+ data = json.loads(result)
+
+ if raw:
+ return str(data), 'en-raw'
+
+ try: language = data[2] # -2][0][0]
+ except: language = '?'
+
+ return ''.join(x[0] for x in data[0]), language
def tr(phenny, context):
"""Translates a phrase, with an optional language hint."""
@@ -37,15 +53,12 @@ def tr(phenny, context):
if (len(phrase) > 350) and (not context.admin):
return phenny.reply('Phrase must be under 350 characters.')
- input = input or detect(phrase)
- if not input:
- err = 'Unable to guess your crazy moon language, sorry.'
- return phenny.reply(err)
+ input = input or 'auto'
input = input.encode('utf-8')
output = (output or 'en').encode('utf-8')
if input != output:
- msg = translate(phrase, input, output)
+ msg, input = translate(phrase, input, output)
if isinstance(msg, str):
msg = msg.decode('utf-8')
if msg:
@@ -56,27 +69,75 @@ def tr(phenny, context):
phenny.reply(msg)
else: phenny.reply('Language guessing failed, so try suggesting one!')
-tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?(?:([a-z]{2}) +)?["“](.+?)["”]\? *$')
+tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$')
tr.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?'
tr.priority = 'low'
+def tr2(phenny, input):
+ """Translates a phrase, with an optional language hint."""
+ command = input.group(2)
+ if not command:
+ return phenny.reply("Need something to translate!")
+ command = command.encode('utf-8')
+
+ def langcode(p):
+ return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
+
+ args = ['auto', 'en']
+
+ for i in xrange(2):
+ if not ' ' in command: break
+ prefix, cmd = command.split(' ', 1)
+ if langcode(prefix):
+ args[i] = prefix[1:]
+ command = cmd
+ phrase = command
+
+ # if (len(phrase) > 350) and (not input.admin):
+ # return phenny.reply('Phrase must be under 350 characters.')
+
+ src, dest = args
+ if src != dest:
+ msg, src = translate(phrase, src, dest)
+ if isinstance(msg, str):
+ msg = msg.decode('utf-8')
+ if msg:
+ msg = web.decode(msg) # msg.replace('&#39;', "'")
+ if len(msg) > 450: msg = msg[:450] + '[...]'
+ msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
+ else: msg = 'The %s to %s translation failed, sorry!' % (src, dest)
+
+ phenny.reply(msg)
+ else: phenny.reply('Language guessing failed, so try suggesting one!')
+
+tr2.commands = ['tr']
+tr2.priority = 'low'
+
def mangle(phenny, input):
+ import time
+
phrase = input.group(2).encode('utf-8')
for lang in ['fr', 'de', 'es', 'it', 'ja']:
- backup = phrase
- phrase = translate(phrase, 'en', lang)
+ backup = phrase[:]
+ phrase, _lang = translate(phrase, 'en', lang)
+ phrase = phrase.encode("utf-8")
+
if not phrase:
- phrase = backup
+ phrase = backup[:]
break
- __import__('time').sleep(0.5)
+ time.sleep(0.25)
+
+ backup = phrase[:]
+ phrase, _lang = translate(phrase, lang, 'en')
+ phrase = phrase.encode("utf-8")
- backup = phrase
- phrase = translate(phrase, lang, 'en')
if not phrase:
- phrase = backup
+ phrase = backup[:]
break
- __import__('time').sleep(0.5)
+ time.sleep(0.25)
+ phrase = phrase.replace(' ,', ',').replace(' .', '.')
+ phrase = phrase.strip(' ,')
phenny.reply(phrase or 'ERRORS SRY')
mangle.commands = ['mangle']
diff --git a/modules/twitter.py b/modules/twitter.py
new file mode 100755
index 0000000..afd1d5c
--- /dev/null
+++ b/modules/twitter.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+"""
+twitter.py - Phenny Twitter Module
+Copyright 2012, Sean B. Palmer, inamidst.com
+Licensed under the Eiffel Forum License 2.
+
+http://inamidst.com/phenny/
+"""
+
+import re, time
+import web
+
+r_username = re.compile(r'^[a-zA-Z0-9_]{1,15}$')
+r_link = re.compile(r'^https?://twitter.com/\S+$')
+r_p = re.compile(r'(?ims)(<p class="js-tweet-text.*?</p>)')
+r_tag = re.compile(r'(?ims)<[^>]+>')
+r_anchor = re.compile(r'(?ims)(<a.*?</a>)')
+r_expanded = re.compile(r'(?ims)data-expanded-url=["\'](.*?)["\']')
+r_whiteline = re.compile(r'(?ims)[ \t]+[\r\n]+')
+r_breaks = re.compile(r'(?ims)[\r\n]+')
+
+def entity(*args, **kargs):
+ return web.entity(*args, **kargs).encode('utf-8')
+
+def decode(html):
+ return web.r_entity.sub(entity, html)
+
+def expand(tweet):
+ def replacement(match):
+ anchor = match.group(1)
+ for link in r_expanded.findall(anchor):
+ return link
+ return r_tag.sub('', anchor)
+ return r_anchor.sub(replacement, tweet)
+
+def read_tweet(url):
+ bytes = web.get(url)
+ shim = '<div class="content clearfix">'
+ if shim in bytes:
+ bytes = bytes.split(shim, 1).pop()
+
+ for text in r_p.findall(bytes):
+ text = expand(text)
+ text = r_tag.sub('', text)
+ text = text.strip()
+ text = r_whiteline.sub(' ', text)
+ text = r_breaks.sub(' ', text)
+ return decode(text)
+ return "Sorry, couldn't get a tweet from %s" % url
+
+def format(tweet, username):
+ return '%s (@%s)' % (tweet, username)
+
+def user_tweet(username):
+ tweet = read_tweet('https://twitter.com/' + username + "?" + str(time.time()))
+ return format(tweet, username)
+
+def id_tweet(tid):
+ link = 'https://twitter.com/twitter/status/' + tid
+ data = web.head(link)
+ message, status = tuple(data)
+ if status == 301:
+ url = message.get("Location")
+ if not url: return "Sorry, couldn't get a tweet from %s" % link
+ username = url.split('/')[3]
+ tweet = read_tweet(url)
+ return format(tweet, username)
+ return "Sorry, couldn't get a tweet from %s" % link
+
+def twitter(phenny, input):
+ arg = input.group(2)
+ if not arg:
+ return phenny.reply("Give me a link, a username, or a tweet id")
+
+ arg = arg.strip()
+ if isinstance(arg, unicode):
+ arg = arg.encode('utf-8')
+
+ if arg.isdigit():
+ phenny.say(id_tweet(arg))
+ elif r_username.match(arg):
+ phenny.say(user_tweet(arg))
+ elif r_link.match(arg):
+ username = arg.split('/')[3]
+ tweet = read_tweet(arg)
+ phenny.say(format(tweet, username))
+ else: phenny.reply("Give me a link, a username, or a tweet id")
+
+twitter.commands = ['tw', 'twitter']
+twitter.thread = True
+
+if __name__ == '__main__':
+ print __doc__
diff --git a/phenny b/phenny
index 6cb0961..1bc110b 100755
--- a/phenny
+++ b/phenny
@@ -59,6 +59,14 @@ def create_default_config(fn):
""")
f.close()
+def create_default_config_file(dotdir):
+ print 'Creating a default config file at ~/.phenny/default.py...'
+ default = os.path.join(dotdir, 'default.py')
+ create_default_config(default)
+
+ print 'Done; now you can edit default.py, and run phenny! Enjoy.'
+ sys.exit(0)
+
def create_dotdir(dotdir):
print 'Creating a config directory at ~/.phenny...'
try: os.mkdir(dotdir)
@@ -68,16 +76,15 @@ def create_dotdir(dotdir):
print >> sys.stderr, 'Please fix this and then run phenny again.'
sys.exit(1)
- print 'Creating a default config file at ~/.phenny/default.py...'
- default = os.path.join(dotdir, 'default.py')
- create_default_config(default)
-
- print 'Done; now you can edit default.py, and run phenny! Enjoy.'
- sys.exit(0)
+ create_default_config_file(dotdir)
def check_dotdir():
+ default = os.path.join(dotdir, 'default.py')
+
if not os.path.isdir(dotdir):
create_dotdir(dotdir)
+ elif not os.path.isfile(default):
+ create_default_config_file(dotdir)
def config_names(config):
config = config or 'default'
@@ -118,7 +125,8 @@ def main(argv=None):
# Step Two: Check Dependencies
check_python_version() # require python2.4 or later
- check_dotdir() # require ~/.phenny, or make it and exit
+ if not opts.config:
+ check_dotdir() # require ~/.phenny, or make it and exit
# Step Three: Load The Configurations
diff --git a/project b/project
new file mode 100755
index 0000000..47b52fd
--- /dev/null
+++ b/project
@@ -0,0 +1,26 @@
+#!/bin/bash
+# project
+# Copyright 2008, Sean B. Palmer, inamidst.com
+# Licensed under the Eiffel Forum License 2.
+
+# archive - Create phenny.tar.bz2 using git archive
+function archive() {
+ git archive --format=tar --prefix=phenny/ HEAD | bzip2 > phenny.tar.bz2
+}
+
+# commit - Check the code into git and push to github
+function commit() {
+ git commit -a && git push origin master
+}
+
+# history - Show a log of recent updates
+function history() {
+ git log --pretty=oneline --no-merges -10
+}
+
+# help - Show functions in project script
+function help() {
+ egrep '^# [a-z]+ - ' $0 | sed 's/# //'
+}
+
+eval "$1"