diff options
-rw-r--r-- | Makefile | 25 | ||||
-rwxr-xr-x | bot.py | 2 | ||||
-rwxr-xr-x | irc.py | 14 | ||||
-rwxr-xr-x | modules/admin.py | 4 | ||||
-rwxr-xr-x | modules/clock.py | 1 | ||||
-rwxr-xr-x | modules/etymology.py | 31 | ||||
-rwxr-xr-x | modules/oblique.py | 4 | ||||
-rwxr-xr-x | modules/remind.py | 57 | ||||
-rwxr-xr-x | modules/search.py | 77 | ||||
-rwxr-xr-x | modules/seen.py | 25 | ||||
-rwxr-xr-x | modules/startup.py | 45 | ||||
-rwxr-xr-x | modules/tell.py | 8 | ||||
-rwxr-xr-x | modules/translate.py | 121 | ||||
-rwxr-xr-x | modules/twitter.py | 93 | ||||
-rwxr-xr-x | phenny | 22 | ||||
-rwxr-xr-x | project | 26 |
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/# //' @@ -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: @@ -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(''', "'") + 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__ @@ -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 @@ -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" |