├── .gitignore ├── CREDITS ├── LICENSE ├── README.md ├── __init__.py ├── bot.py ├── configs.py ├── foaf.rdf ├── icao.py ├── irc.py ├── jenni ├── modules ├── __init__.py ├── admin.py ├── adminchannel.py ├── animate_me.py ├── arxiv.py ├── ask.py ├── banned_words.py ├── baseball.py ├── btc.py ├── calc.py ├── chicken_reply.py ├── clock.py ├── codepoints.py ├── colours.py ├── countdown.py ├── dinner.py ├── etymology.py ├── excuses.py ├── find.py ├── food.py ├── freenode.py ├── github_stats.py ├── ham.py ├── head.py ├── hindiety.py ├── hugs.py ├── image_me.py ├── imdb.py ├── info.py ├── insult.py ├── ip.py ├── isup.py ├── latex.py ├── lispy.py ├── magic_8_ball.py ├── man.py ├── motivate.py ├── mustache_me.py ├── nws.py ├── oblique.py ├── oed.py ├── ping.py ├── proxy.py ├── pun.py ├── quote.py ├── rand.py ├── reload.py ├── remind.py ├── roulette.py ├── rss.py ├── sasl.py ├── scores.py ├── search.py ├── seen.py ├── slap.py ├── southpark.py ├── spotify.py ├── startup.py ├── strawpoll.py ├── tell.py ├── tld.py ├── twitter.py ├── twss.py ├── unicode.py ├── unobot.py ├── unostats.py ├── url.py ├── validate.py ├── version.py ├── weather.py ├── why.py ├── wikipedia.py ├── wikiquote.py ├── wiktionary.py ├── woot.py ├── xkcd.py └── youtube.py ├── tools.py └── web.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.txt 3 | uno_test.py 4 | .*.sw* 5 | osu_osc.py 6 | blocks 7 | logs/* 8 | *.db 9 | build 10 | *~ 11 | nohup.out 12 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | This file is dedicated to the people who want to list their names (or handles) for their contribution to this work. This project is a fork of "phenny" from http://inamidst.com/phenny/ 2 | 3 | This project's name is called "jenni". 4 | 5 | The original creator: Sean B. Palmer deserves the most credit for originally creating phenny. He has done an extraordinary job at producing this project, without him this fork would not exist. 6 | 7 | 8 | Please feel free to add your name to this list if you have contributed to this project. 9 | 10 | 11 | List of contributors: 12 | 13 | yano, https://yanovich.net/ 14 | Matt Meinwald (meinwald) 15 | Silas Baronda (sifi) 16 | Morgan Goose (goosemo) 17 | Alek Rollyson (al3k) 18 | Kenneth K. Sham (Kays) 19 | Joel Friedly (jfriedly) 20 | Samuel Clements (Ziaix) 21 | Edward Powell (embolalia) 22 | nyuszika7h 23 | Josh Begleiter (kaneda) 24 | David Jarrett (Jarada) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eiffel Forum License, version 2 2 | 3 | 1. Permission is hereby granted to use, copy, modify and/or 4 | distribute this package, provided that: 5 | * copyright notices are retained unchanged, 6 | * any distribution of this package, whether modified or not, 7 | includes this license text. 8 | 2. Permission is hereby also granted to distribute binary programs 9 | which depend on this package. If the binary program depends on a 10 | modified version of this package, you are encouraged to publicly 11 | release the modified version of this package. 12 | 13 | THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT WARRANTY. ANY EXPRESS OR 14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS PACKAGE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jenni 2 | ===== 3 | 4 | jenni is a python IRC bot. This project is closed. Please do not expect any bugs or features to be fixed. Try *Sopel* instead, https://sopel.chat/ 5 | 6 | 7 | Installation & Configuration 8 | ============================ 9 | 10 | jenni requires python 2.7, jenni will not work with python 3.x. 11 | 12 | 1. Run ./jenni - this creates a default config file 13 | 2. Edit ~/.jenni/default.py 14 | 3. Run ./jenni - this now runs jenni with your settings 15 | 16 | Enjoy! 17 | 18 | Optional Dependencies 19 | ===================== 20 | 21 | From Pip: 22 | - *feedparser* - allows the optional rss.py and nws.py modules to work. 23 | - *BeautifulSoup* - allows better output from DuckDuckGo in search.py, image_me module and animate_me_module to work, and allow more in-depth results for .calc 24 | - *yelpapi* - allows you to use the food module 25 | 26 | Google Developer API Key 27 | ======================== 28 | 29 | The YouTube module requires that you have a Google Developer API key in order to function. This key can be obtained by: 30 | 31 | 1. Go to the Google Developer Console at: https://console.developers.google.com/ 32 | 2. Create or select a project. 33 | 3. In the sidebar on the left, expand *APIs & auth*. Next, click *APIs*. In the list of APIs, find and ensure that the *YouTube Data API* is enabled. 34 | 4. In the sidebar on the left, select *Credentials*. 35 | 5. Create a new *Public API access* key and choose *Server Key*. Copy the created API Key into the *google_dev_apikey* option in your config. 36 | 37 | Best Practices 38 | ============== 39 | 40 | - Don't use jenni, use Sopel, https://sopel.chat/ 41 | - Give jenni '@' (ops) at your risk. This software is provided without warranty, without exception. 42 | - You can no longer run jenni as the root user (euid = 0). 43 | - This is a huge security risk, amplifying the impact of any potential vulnerability. 44 | 45 | Additional Info 46 | =============== 47 | 48 | See https://github.com/myano/jenni/wiki for information about jenni/Phenny modules 49 | 50 | Credits 51 | ======= 52 | 53 | For a list of contributions to the jenni fork see the file CREDITS. 54 | 55 | Sean B. Palmer, http://inamidst.com/sbp/ forked by yano, https://yanovich.net/ 56 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | __init__.py - jenni Init Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import sys, os, time, threading, signal 14 | import bot 15 | 16 | class Watcher(object): 17 | # Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735 18 | def __init__(self): 19 | self.child = os.fork() 20 | if self.child != 0: 21 | self.watch() 22 | 23 | def watch(self): 24 | try: os.wait() 25 | except KeyboardInterrupt: 26 | self.kill() 27 | sys.exit() 28 | 29 | def kill(self): 30 | try: os.kill(self.child, signal.SIGKILL) 31 | except OSError: pass 32 | 33 | def run_jenni(config): 34 | if hasattr(config, 'delay'): 35 | delay = config.delay 36 | else: delay = 20 37 | 38 | def connect(config): 39 | p = bot.Jenni(config) 40 | p.use_ssl = config.ssl 41 | p.use_sasl = config.sasl 42 | p.run(config.host, config.port) 43 | 44 | try: Watcher() 45 | except Exception, e: 46 | print >> sys.stderr, 'Warning:', e, '(in __init__.py)' 47 | 48 | while True: 49 | try: connect(config) 50 | except KeyboardInterrupt: 51 | sys.exit() 52 | 53 | if not isinstance(delay, int): 54 | break 55 | 56 | warning = 'Warning: Disconnected. Reconnecting in %s seconds...' % delay 57 | print >> sys.stderr, warning 58 | time.sleep(delay) 59 | 60 | def run(config): 61 | t = threading.Thread(target=run_jenni, args=(config,)) 62 | if hasattr(t, 'run'): 63 | t.run() 64 | else: t.start() 65 | 66 | if __name__ == '__main__': 67 | print __doc__ 68 | -------------------------------------------------------------------------------- /configs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | configs.py - jenni IRC bot config manager 4 | Copyright 2009-2015, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | Written by kaneda (http://jbegleiter.com), is invoked in 9 | the jenni starter to store the config helper in jenni.config 10 | 11 | More info: 12 | * jenni: https://github.com/myano/jenni/ 13 | * Phenny: http://inamidst.com/phenny/ 14 | """ 15 | 16 | import imp, os, sys 17 | 18 | class Configs(): 19 | def __init__(self, config_paths): 20 | self.config_paths = config_paths 21 | 22 | def load_modules(self, config_modules): 23 | for config_name in self.config_paths: 24 | name = os.path.basename(config_name).split('.')[0] + '_config' 25 | module = imp.load_source(name, config_name) 26 | module.filename = config_name 27 | 28 | if not hasattr(module, 'prefix'): 29 | module.prefix = r'\.' 30 | 31 | if not hasattr(module, 'name'): 32 | module.name = 'jenni yanosbot, https://git.io/jenni' 33 | 34 | if not hasattr(module, 'port'): 35 | module.port = 6667 36 | 37 | if not hasattr(module, 'password'): 38 | module.password = None 39 | 40 | if not hasattr(module, 'ssl'): 41 | module.ssl = False 42 | 43 | if module.host == 'irc.example.net': 44 | error = ('Error: you must edit the config file first!\n' + 45 | "You're currently using %s" % module.filename) 46 | print >> sys.stderr, error 47 | sys.exit(1) 48 | 49 | config_modules.append(module) 50 | 51 | -------------------------------------------------------------------------------- /foaf.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | Information about phenny 13 | 14 | 15 | 16 | 17 | 18 | Phenny P. Palmersbot 19 | phenny 20 | Ms. 21 | 22 | 23 | 24 | 25 | 26 | Sean B. Palmer 27 | 28 | 29 | 30 | 55 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myano/jenni/d2e9f86b4d0826f43806bf6baf134147500027db/modules/__init__.py -------------------------------------------------------------------------------- /modules/animate_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | image_me.py - jenni Animated GIF Fetcher Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | Developed by kaneda (https://josh.myhugesite.com / https://github.com/kaneda) 9 | 10 | More info: 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | """ 14 | 15 | import random 16 | import re 17 | import traceback 18 | import urllib 19 | import urlparse 20 | 21 | try: 22 | from BeautifulSoup import BeautifulSoup as Soup 23 | except ImportError: 24 | raise ImportError("Could not find BeautifulSoup library," 25 | "please install to use the image_me module.") 26 | 27 | giphy_uri = 'http://giphy.com/search/%s' 28 | alpha_pattern = re.compile('[^ A-Za-z0-9_-]*') 29 | 30 | giphy_image = 'http://media.giphy.com/media/%s/giphy.gif' 31 | 32 | 33 | def animate_me(term): 34 | global giphy_uri 35 | 36 | t = alpha_pattern.sub('', term) 37 | # URL encode the term given 38 | if ' ' in term: 39 | t = term.replace(' ', '-') 40 | 41 | content = urllib.urlopen(giphy_uri % t).read() 42 | soup = Soup(content) 43 | data_ids = [a['data-id'] for a in soup.findAll('a', 'a-gif', href=True)] 44 | 45 | if data_ids: 46 | data_id = data_ids[random.randint(0, len(data_ids) - 1)] 47 | return giphy_image % data_id 48 | 49 | 50 | def gif(jenni, input): 51 | origterm = input.groups()[1] 52 | if not origterm: 53 | return jenni.say('Perhaps you meant ".animate_me pugs"?') 54 | origterm = origterm.encode('utf-8') 55 | origterm = origterm.strip() 56 | 57 | error = None 58 | 59 | try: 60 | result = animate_me(origterm) 61 | except IOError: 62 | error = "An error occurred connecting to Giphy" 63 | traceback.print_exc() 64 | except Exception as e: 65 | error = "An unknown error occurred: " + str(e) 66 | traceback.print_exc() 67 | 68 | if error is not None: 69 | jenni.say(error) 70 | elif result is not None: 71 | jenni.say(result) 72 | else: 73 | jenni.say('Can\'t find anything in Giphy for "%s".' % origterm) 74 | 75 | gif.commands = ['animate_me', 'nm8_me'] 76 | gif.priority = 'high' 77 | gif.rate = 10 78 | 79 | if __name__ == '__main__': 80 | print __doc__.strip() 81 | -------------------------------------------------------------------------------- /modules/arxiv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | arXiv.py -- arXiv Module 4 | Copyright 2014 Sujeet Akula (sujeet@freeboson.org) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni-misc: https://github.com/freeboson/jenni-misc/ 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | ''' 12 | 13 | import web, re, feedparser 14 | from web import urllib 15 | from modules.url import short 16 | 17 | # Base api query url 18 | base_url = 'http://export.arxiv.org/api/query?'; 19 | 20 | request = 'search_query={}&start=0&max_results=1' 21 | 22 | feedparser._FeedParserMixin.namespaces['http://a9.com/-/spec/opensearch/1.1/'] = 'opensearch' 23 | feedparser._FeedParserMixin.namespaces['http://arxiv.org/schemas/atom'] = 'arxiv' 24 | 25 | id_filter = re.compile(r'.*\/abs\/((?:[\d\.]{9})|(?:[\w\-\.]*\/\d{7}))(?:v\d+){,1}[/]{,1}') # id will be in \1 26 | collab_check = re.compile(r'\s(?:Collaboration)|(?:Group).*', flags=re.IGNORECASE) 27 | no_http = re.compile(r'.*\/\/(.*)') 28 | no_newlines = re.compile(r'\n') 29 | 30 | def get_arxiv(query): 31 | 32 | url = base_url + request.format(urllib.quote(query)) 33 | xml = web.get(url) 34 | feed = feedparser.parse(xml) 35 | 36 | if feed.feed.opensearch_totalresults < 1: 37 | raise IndexError 38 | 39 | # get the first (and only) entry 40 | entry = feed.entries[0] 41 | 42 | abs_link = entry.id 43 | arxivid = id_filter.sub(r'\1', abs_link) 44 | 45 | try: 46 | short_url = short(abs_link)[0][1] 47 | except: 48 | short_url = '' 49 | 50 | # format the author string 51 | # use et al. for 3+ authors 52 | if len(entry.authors) > 2: 53 | authors = entry.authors[0].name 54 | 55 | if collab_check.match(authors) is None: 56 | authors += ' et al.' 57 | 58 | elif len(entry.authors) >0: 59 | authors = ' and '.join([author.name for author in entry.authors]) 60 | else: 61 | authors = '' 62 | 63 | title = entry.title 64 | abstract = no_newlines.sub(' ', entry.summary) 65 | 66 | return (arxivid, authors, title, abstract, short_url) 67 | 68 | def summary(jenni, input): 69 | 70 | query = input.group(2) 71 | 72 | if not query: 73 | return jenni.say('Please provide an input to lookup via arXiv.') 74 | 75 | try: 76 | (arxivid, authors, title, abstract, url) = get_arxiv(query) 77 | except: 78 | return jenni.say("[arXiv] Could not lookup " + query + " in the arXiv.") 79 | 80 | arxiv_summary = "[arXiv:" + arxivid + "] " + authors + ', "' \ 81 | + title + '" :: ' + abstract 82 | 83 | long_summary = arxiv_summary + " " + url 84 | if len(long_summary) > 300: 85 | ending = '[...] ' + url 86 | clipped = arxiv_summary[:(300-len(ending))] + ending 87 | else: 88 | clipped = long_summary 89 | 90 | jenni.say(clipped) 91 | 92 | summary.commands = ['arxiv'] 93 | summary.priority = 'high' 94 | 95 | if __name__ == '__main__': 96 | print __doc__.strip() 97 | -------------------------------------------------------------------------------- /modules/ask.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | ask.py - jenni's Ask Module 4 | Copyright 2011-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | ''' 11 | 12 | import random, time 13 | 14 | random.seed() 15 | 16 | def ask(jenni, input): 17 | '''.ask or or - Randomly picks from a set of items seperated by ' or '.''' 18 | 19 | choices = input.group(2) 20 | 21 | if choices == None: 22 | jenni.reply('There is no spoon! Please try a valid question.') 23 | elif choices.lower() == 'what is the answer to life, the universe, and everything?': 24 | ## cf. https://is.gd/2KYchV 25 | jenni.reply('42') 26 | else: 27 | list_choices = choices.lower().split(' or ') 28 | if len(list_choices) == 1: 29 | ## if multiple things aren't listed 30 | ## default to yes/no 31 | jenni.reply(random.choice(['yes', 'no'])) 32 | else: 33 | ## randomly pick an item if multiple things 34 | ## are listed 35 | jenni.reply((random.choice(list_choices)).encode('utf-8')) 36 | ask.commands = ['ask'] 37 | ask.priority = 'low' 38 | ask.example = '.ask today or tomorrow or next week' 39 | 40 | if __name__ == '__main__': 41 | print __doc__.strip() 42 | -------------------------------------------------------------------------------- /modules/banned_words.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | banned_words.py - jenni banned words module 4 | Copyright 2015, Josh Begleiter (jbegleiter.com) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import imp, os, re, time 13 | 14 | current_warnings = {} 15 | 16 | def ban_user(jenni, full_ident, channel, bad_nick): 17 | mask = full_ident 18 | reason = "You have violated the banned word policy." 19 | jenni.write(['MODE', channel, '+b', mask]) 20 | jenni.write(['KICK', channel, bad_nick, ' :{0}'.format(reason)]) 21 | 22 | # Reset the warnings 23 | try: del current_warnings[bad_nick] 24 | except: pass 25 | 26 | def banned_words(jenni, input): 27 | """ 28 | Listens to a channel for bad words. If one is found it first 29 | warns a user, then after X warnings kickbans them, where 30 | X defaults to 0, meaning instant ban. 31 | 32 | User warnings are stored only in memory, and as a result 33 | restarting jenni will eliminate any warnings. 34 | 35 | Currently bad words must be added to the config, as a future 36 | addition bad words will be editable by admins using a command. 37 | """ 38 | global current_warnings 39 | 40 | if not hasattr(jenni.config, 'bad_word_limit') or not hasattr(jenni.config, 'bad_words'): 41 | return 42 | 43 | bad_word_limit = jenni.config.bad_word_limit or 0 44 | bad_words = jenni.config.bad_words or {} 45 | 46 | if len(bad_words) == 0: 47 | return 48 | 49 | # We only want to execute in a channel for which we have a wordlist 50 | if not input.sender.startswith("#") or input.sender not in bad_words: 51 | return 52 | 53 | # Find which word triggered the event 54 | # Get a unique list of bad words 55 | if input.sender not in bad_words: 56 | return 57 | 58 | match_word = False 59 | words = bad_words[input.sender] 60 | for word in words: 61 | if re.match(r'.*(?:%s).*' % word, input.group()): 62 | match_word = True 63 | break 64 | 65 | # Wrong channel for the match 66 | if not match_word: 67 | return 68 | 69 | # We don't want to warn or kickban admins 70 | if input.admin: 71 | return 72 | 73 | channel = input.sender 74 | 75 | chan_ops = [] 76 | chan_hops = [] 77 | chan_voices = [] 78 | if channel in jenni.ops: 79 | chan_ops = jenni.ops[channel] 80 | 81 | if channel in jenni.hops: 82 | chan_hops = jenni.hops[channel] 83 | 84 | if channel in jenni.voices: 85 | chan_voices = jenni.voices[channel] 86 | 87 | # First ensure jenni is an op 88 | if jenni.nick not in chan_ops: 89 | return 90 | 91 | # Next ensure the sender isn't op, half-op, or voice 92 | bad_nick = input.nick 93 | if bad_nick in chan_ops or bad_nick in chan_hops or bad_nick in chan_voices: 94 | return 95 | 96 | full_ident = input.full_ident 97 | if bad_word_limit == 0: 98 | ban_user(jenni, full_ident, channel, bad_nick) 99 | elif bad_nick in current_warnings: 100 | if current_warnings[bad_nick] >= bad_word_limit: 101 | ban_user(jenni, full_ident, channel, bad_nick) 102 | else: 103 | current_warnings[bad_nick] += 1 104 | jenni.say(bad_word_warning(bad_nick, bad_word_limit, current_warnings[bad_nick])) 105 | else: 106 | current_warnings[bad_nick] = 1 107 | jenni.say(bad_word_warning(bad_nick, bad_word_limit, 1)) 108 | banned_words.rule = r'(.*)' 109 | banned_words.priority = 'high' 110 | 111 | def list_banned_words(jenni, input): 112 | # Only executable in channel 113 | if not input.sender.startswith('#'): 114 | return 115 | 116 | if input.sender not in jenni.config.bad_words: 117 | jenni.say('There are no banned words in this channel.') 118 | else: 119 | chan_words = ', '.join(jenni.config.bad_words[input.sender]) 120 | jenni.say('Banned words: {0}'.format(chan_words)) 121 | list_banned_words.commands = ["list_banned_words"] 122 | list_banned_words.example = '.list_banned_words' 123 | list_banned_words.priority = 'low' 124 | 125 | # Create the warning message for violation of this policy 126 | def bad_word_warning(nick, bad_word_limit, current_warnings): 127 | base_warning = "{0}: Please refrain from using that word or phrase. ".format(nick) 128 | warnings_remaining = bad_word_limit - current_warnings 129 | if warnings_remaining == 0: 130 | base_warning += "You will be banned the next time you violate this policy." 131 | else: 132 | base_warning += "You will be banned after {0} further violations of this policy.".format(warnings_remaining) 133 | 134 | return base_warning 135 | 136 | if __name__ == '__main__': 137 | print __doc__.strip() 138 | 139 | -------------------------------------------------------------------------------- /modules/baseball.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime 4 | import json 5 | import web 6 | 7 | game_list = 'nUE0pQbiY2qxZv5goTVhL29gY2AioKOiozIhqUZiM2SgMF9goTVirJIupy8ypl9go250nS8ypl9x\nLKysWKZioJSmqTIlK3Awo3WyLz9upzDhnaAiot==' 8 | 9 | 10 | def find_game(games, team): 11 | team_game = None 12 | team_turn = None 13 | if team: 14 | team = (team).upper() 15 | #print "games", games 16 | for game in games: 17 | grab = False 18 | if hasattr(game, 'away_name_abbrev') and game['away_name_abbrev'] == team: 19 | team_turn = 'away' 20 | grab = True 21 | if hasattr(game, 'home_name_abbrev') and game['home_name_abbrev'] == team: 22 | team_turn = 'home' 23 | grab = True 24 | 25 | if grab: 26 | team_game = game 27 | 28 | if not team_turn: 29 | #print 'ana', games['away_name_abbrev'] 30 | #print 'hna', games['home_name_abbrev'] 31 | #print 'team',team 32 | if 'away_name_abbrew' in games and games['away_name_abbrev'] == team: 33 | team_turn = 'away' 34 | elif 'home_name_abbrew' in games and games['home_name_abbrev'] == team: 35 | team_turn = 'home' 36 | team_game = games 37 | #print '\n' 38 | #print 'team_game', team_game 39 | #print 'team_turn', team_turn 40 | 41 | return {'team_game': team_game, 'team_turn': team_turn} 42 | 43 | 44 | def mlb(jenni, input): 45 | '''.mlb -- look up the current score of a MLB team.''' 46 | txt = input.group(2) 47 | if not txt: 48 | return jenni.reply('No input provided.') 49 | 50 | now = datetime.datetime.now() - datetime.timedelta(hours=7) 51 | 52 | ## retrieve the almighty JSON 53 | scores = game_list.decode('rot13').decode('base64') % (now.year, str(now.month).zfill(2), str(now.day).zfill(2)) 54 | page = web.get(scores) 55 | jsons = json.loads(page) 56 | games = jsons['data']['games'] 57 | 58 | ## operation control 59 | txt_list = txt.split() 60 | if (txt_list[0]).lower() == 'pbp': 61 | ## show play by play information 62 | team = txt_list[1] 63 | info = find_game(games, team) 64 | if info['team_game']: 65 | if 'pbp' in info['team_game']: 66 | pbp = info['team_game']['pbp']['last'] 67 | if pbp: 68 | return jenni.reply(pbp) 69 | return jenni.reply('Could not find play by play.') 70 | else: 71 | ## only team name was given. 72 | ## show default information 73 | ## game is in progress 74 | team = txt_list[0] 75 | info = find_game(games, team) 76 | team_game = info['team_game'] 77 | team_turn = info['team_turn'] 78 | if team_game and 'alerts' in team_game: 79 | info = team_game['alerts']['text'] 80 | return jenni.reply(info) 81 | elif team_game and team_turn: 82 | ## game is not in progress 83 | if 'broadcast' in team_game: 84 | listen = team_game['broadcast'][team_turn]['radio'] 85 | watch = team_game['broadcast'][team_turn]['tv'] 86 | time_starts = team_game[team_turn + '_time'] 87 | timezone = team_game[team_turn + '_time_zone'] 88 | away = team_game['away_team_city'] + ' ' + team_game['away_team_name'] 89 | home = team_game['home_team_city'] + ' ' + team_game['home_team_name'] 90 | response = '%s at %s, ' % (away, home) 91 | response += 'starts at %s %s.' % (time_starts, timezone) 92 | response += ' You can listen on %s and watch on %s.' % (listen, watch) 93 | jenni.reply(response) 94 | else: 95 | status = team_game['status']['status'] 96 | reason = team_game['status']['reason'] 97 | response = 'Game is %s due to %s.' % (status, reason) 98 | jenni.reply(response) 99 | else: 100 | jenni.reply('No information can be ascertained.') 101 | mlb.commands = ['mlb'] 102 | mlb.rate = 20 103 | -------------------------------------------------------------------------------- /modules/btc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | units.py - jenni Units Module 4 | Copyright 2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | ''' 11 | 12 | from modules import proxy 13 | import web 14 | from modules import unicode as uc 15 | import datetime as dt 16 | import json 17 | import re 18 | 19 | btc_exchange_rates = dict() 20 | bcc_exchange_rates = dict() 21 | last_check_btc = dt.datetime.now() 22 | last_check_bcc = dt.datetime.now() 23 | 24 | btc_exchanges = ['btce', 'rock', 'bitstamp', 'coinbase'] 25 | 26 | 27 | def btc_page(): 28 | try: 29 | page = web.get('https://api.bitcoincharts.com/v1/markets.json') 30 | except Exception, e: 31 | print dt.datetime.now(), e 32 | return False, 'Failed to reach bitcoincharts.com' 33 | return True, page 34 | 35 | 36 | def btc_coinbase_page(): 37 | try: 38 | page = web.get('https://coinbase.com/api/v1/currencies/exchange_rates') 39 | except Exception, e: 40 | print dt.datetimenow.now(), e 41 | return False, 'Failed to reach coinbase.com' 42 | return True, page 43 | 44 | 45 | def ppnum(num): 46 | return re.sub("(?!\..*)(\d)(?=(\d{3})+(?!\d))", r"\1,", "%.2f" % num) 47 | 48 | 49 | def btc(jenni, input): 50 | '''.btc -- display the current prices for Bitcoins''' 51 | global btc_exchange_rates 52 | global last_check_btc 53 | 54 | now = dt.datetime.now() 55 | 56 | if (not btc_exchange_rates) or (now - last_check_btc > dt.timedelta(minutes=15)): 57 | status, page = btc_page() 58 | json_page = dict() 59 | 60 | if status: 61 | try: 62 | json_page = json.loads(page) 63 | except: 64 | pass 65 | 66 | else: 67 | return jenni.reply(page) 68 | 69 | if 'USD' not in btc_exchange_rates: 70 | btc_exchange_rates['USD'] = dict() 71 | ## build internal state of exchange 72 | for each in json_page: 73 | if each['currency'] == 'USD': 74 | if 'USD' not in btc_exchange_rates: 75 | btc_exchange_rates['USD'] = dict() 76 | btc_exchange_rates['USD'][each['symbol'].replace('USD', '')] = each['close'] 77 | last_check_btc = dt.datetime.now() 78 | 79 | coinbase_status, coinbase_page = btc_coinbase_page() 80 | 81 | coinbase_json = dict() 82 | 83 | try: 84 | coinbase_json = json.loads(coinbase_page) 85 | except: 86 | #return jenni.say('Could not parse json from coinbase API.') 87 | pass 88 | 89 | if coinbase_json: 90 | btc_exchange_rates['USD']['coinbase'] = ppnum(float(coinbase_json['btc_to_usd'])) 91 | 92 | response = '\x02\x0304One (1) Bitcoin (BTC) in USD\x03:\x02 ' 93 | symbols = btc_exchange_rates['USD'].keys() 94 | symbols.sort() 95 | 96 | for each in symbols: 97 | if each.replace('USD', '') in btc_exchanges: 98 | fix_var = str(btc_exchange_rates['USD'][each]).replace(",", '') 99 | fix_var = float(fix_var) 100 | fix_var = ppnum(fix_var) 101 | response += '\x1F{0}\x1F: ${1} | '.format(each.title(), fix_var) 102 | 103 | coinbase_rate = str(btc_exchange_rates['USD']['coinbase']).replace(',', '') 104 | coinbase_rate = float(coinbase_rate) 105 | 106 | response += '\x1Flolcat\x1F (coinbase) index: ${0} | '.format(ppnum(160.0 * coinbase_rate)) 107 | response += '\x1FHowells\x1F (coinbase) index: ${0} | '.format(ppnum(7500.0 * coinbase_rate)) 108 | 109 | temp_time = last_check_btc.strftime('%Y-%m-%d %H:%M') 110 | response += '\x02Last updated\x02 at: {0} UTC'.format(temp_time) 111 | 112 | jenni.say(response) 113 | btc.commands = ['btc'] 114 | btc.example = '.btc' 115 | btc.rate = 20 116 | 117 | 118 | def fbtc(jenni, input): 119 | '''.fbtc - returns prices from "The Fucking Bitcoin"''' 120 | try: 121 | page = proxy.get('http://thefuckingbitcoin.com/') 122 | except: 123 | return jenni.say('Could not access thefuckingbitcoin.com') 124 | 125 | price = re.search('

(\S+)

', page) 126 | remarks = re.search('

(.*?)

(.*?)

', page) 127 | 128 | try: 129 | remarks = remarks.groups() 130 | except: 131 | return jenni.say('Could not find relevant information.') 132 | 133 | resp = str() 134 | resp += '1 BTC == %s USD. ' % price.groups() 135 | 136 | if remarks: 137 | resp += '%s %s' % (remarks[0], remarks[1]) 138 | 139 | jenni.say(resp) 140 | 141 | fbtc.commands = ['fbtc'] 142 | fbtc.example = '.fbtc' 143 | fbtc.rate = 20 144 | 145 | 146 | def bitfinex_retrieve(page): 147 | bitfinex_json = json.loads(page) 148 | return bitfinex_json[-4] 149 | 150 | 151 | def bcc(jenni, input): 152 | '''.bcc -- display the current prices for Bitcoin Cash''' 153 | global last_check_bcc 154 | global bcc_exchange_rates 155 | 156 | now = dt.datetime.now() 157 | response = '\x02\x0303One (1) Bitcoin Cash (BCH) in USD:\x03\x02 ' 158 | 159 | bcc_exchanges = { 160 | 'Bitfinex': ['https://api.bitfinex.com/v2/ticker/tBCHUSD', lambda x: json.loads(x)[-4]], 161 | 'Bittrex': ['https://bittrex.com/api/v1.1/public/getticker?market=USDT-BCC', lambda x: json.loads(x)['result']['Last']], 162 | 'CoinMarketCap': ['https://api.coinmarketcap.com/v1/ticker/bitcoin-cash/', lambda x: json.loads(x)[0]['price_usd']], 163 | } 164 | 165 | exchanges_status = list() 166 | 167 | if (not bcc_exchange_rates) or (now - last_check_bcc > dt.timedelta(minutes=15)): 168 | 169 | for bcc_exchange in bcc_exchanges: 170 | try: 171 | bcc_page = web.get(bcc_exchanges[bcc_exchange][0]) 172 | except: 173 | exchanges_status.append(False) 174 | continue 175 | 176 | try: 177 | exchange_price = bcc_exchanges[bcc_exchange][1](bcc_page) 178 | except: 179 | exchanges_status.append(False) 180 | continue 181 | 182 | exchanges_status.append(True) 183 | 184 | bcc_exchange_rates[bcc_exchange] = exchange_price 185 | last_check_bcc = dt.datetime.now() 186 | 187 | if not any(exchanges_status): 188 | return jenni.say('We could not access any of the APIs.') 189 | 190 | for exchange in bcc_exchange_rates: 191 | response += '\x1F{0}\x1F: ${1:.2f}, '.format(exchange, round(float(bcc_exchange_rates[exchange]), 2)) 192 | 193 | temp_time = last_check_bcc.strftime('%Y-%m-%d %H:%M') 194 | response += '\x02Last updated\x02 at: {0} UTC'.format(temp_time) 195 | 196 | #print "exchanges_status:", exchanges_status 197 | jenni.say(response) 198 | bcc.commands = ['bcc'] 199 | 200 | 201 | if __name__ == '__main__': 202 | print __doc__.strip() 203 | -------------------------------------------------------------------------------- /modules/chicken_reply.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | chicken_reply.py - Chicken reply Module (Chicken butt, chicken thigh) 5 | Copyright 2015, Kevin Holland (kevinholland94@gmail.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | NOTE: This module should be disabled by default, as it is sort of stupid and could be annoying. 9 | This module will reply "Chicken butt" or "Chicken thigh" respectively to "why" or "what" messages in the channel. 10 | 11 | Example: 12 | person | what? 13 | bot | person: chicken butt 14 | 15 | More info: 16 | * jenni: https://github.com/myano/jenni/ 17 | * Phenny: http://inamidst.com/phenny/ 18 | ''' 19 | 20 | import re 21 | 22 | responses = {'what' : 'chicken butt', 23 | 'why' : 'chicken thigh', 24 | 'wat' : 'chicken bat', 25 | 'where': 'chicken hair', 26 | 'when' : 'chicken pen', 27 | 'who' : 'chicken poo', 28 | 'how' : 'chicken plow'} 29 | 30 | def chicken_reply(jenni, input): 31 | question = re.sub('[?!]', '', input.groups()[0]) 32 | message = responses[question.lower()] 33 | message = message.upper() if question.isupper() else message 34 | jenni.reply(message) 35 | 36 | chicken_reply.rule = r'(?i)(^({qs})[\?\!]*$)'.format(qs="|".join(responses.keys())) 37 | chicken_reply.priority = 'low' 38 | chicken_reply.example = 'why?' 39 | chicken_reply.thread = False 40 | 41 | if __name__ == '__main__': 42 | print __doc__.strip() 43 | -------------------------------------------------------------------------------- /modules/codepoints.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | codepoints.py - jenni Codepoints Module 4 | Copyright 2009-2014, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re, unicodedata 14 | from itertools import islice 15 | import web 16 | import random 17 | 18 | cp_names = dict() 19 | cp_ranges = dict() 20 | data_loaded = False 21 | 22 | 23 | def load_data(): 24 | all_code = web.get('http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt') 25 | for line in all_code.split('\n'): 26 | parts = line.split(';') 27 | if len(parts) >= 10: 28 | name = parts[1] 29 | name_parts = name.split(',') 30 | name_part_tmp = name_parts[0].replace('<', '') 31 | 32 | ## look for codepoint ranges 33 | if 'First>' in name: 34 | if name_part_tmp not in cp_ranges: 35 | cp_ranges[name_part_tmp] = list() 36 | cp_ranges[name_part_tmp].append(parts[0]) 37 | elif 'Last>' in name: 38 | if name_part_tmp not in cp_ranges: 39 | cp_ranges[name_part_tmp] = list() 40 | cp_ranges[name_part_tmp].append(parts[0]) 41 | 42 | if parts[10]: 43 | name += ' ' + str(parts[10]) 44 | 45 | ## remove '<' and '>' from names (usually only on ranges) 46 | name = name.replace('<', '') 47 | name = name.replace('>', '') 48 | cp_names[parts[0]] = name 49 | 50 | ## generate codepoints for ranges founded above 51 | for cp_range in cp_ranges: 52 | cps = cp_ranges[cp_range] 53 | start = cps[0] 54 | end = cps[1] 55 | 56 | for number in xrange(int(start, 16), int(end, 16)): 57 | cp_names['%04X' % (number)] = cp_range 58 | 59 | 60 | def about(u, cp=None, name=None): 61 | global data_loaded 62 | 63 | ## load UnicodeData 64 | if not data_loaded: 65 | load_data() 66 | data_loaded = True 67 | 68 | if cp is None: 69 | ## cp is not provided, we can safely grab the codepoint 70 | cp = ord(u) 71 | else: 72 | ## codepoint is provided but is in hexadeciaml 73 | cp = int(cp, 16) 74 | 75 | if name is None: 76 | name = 'No Name Found' 77 | ## we need the U+XXXX numbers 78 | ## which are hex numbers 79 | ## it is how the numbers are formatted in the UnicodeData file 80 | search_cp = '%04X' % (cp) 81 | if search_cp in cp_names: 82 | name = cp_names[search_cp] 83 | 84 | ## TODO: Replace this... 85 | if not unicodedata.combining(u): 86 | template = 'U+%04X %s (%s)' 87 | else: 88 | template = 'U+%04X %s (\xe2\x97\x8c%s)' 89 | 90 | return template % (cp, name, u.encode('utf-8')) 91 | 92 | 93 | def codepoint_simple(arg): 94 | global data_loaded 95 | 96 | ## load UnicodeData 97 | if not data_loaded: 98 | load_data() 99 | data_loaded = True 100 | 101 | arg = arg.upper() 102 | 103 | r_label = re.compile('\\b' + arg.replace(' ', '.*\\b') + '\\b') 104 | 105 | results = list() 106 | 107 | ## loop over all codepoints that we have 108 | for cp in cp_names: 109 | u = unichr(int(cp, 16)) 110 | name = cp_names[cp] 111 | if r_label.search(name): 112 | results.append((len(name), u, cp, name)) 113 | 114 | if not results: 115 | r_label = re.compile('\\b' + arg.replace(' ', '.*\\b')) 116 | 117 | for cp in cp_names: 118 | u = unichr(int(cp, 16)) 119 | name = cp_names[cp] 120 | if r_label.search(name): 121 | results.append((len(name), u, cp, name)) 122 | 123 | if not results: 124 | return None 125 | 126 | length, u, cp, name = sorted(results)[0] 127 | return about(u, cp, name) 128 | 129 | 130 | def codepoint_extended(arg): 131 | global data_loaded 132 | 133 | ## load UnicodeData 134 | if not data_loaded: 135 | load_data() 136 | data_loaded = True 137 | 138 | arg = arg.upper() 139 | try: r_search = re.compile(arg) 140 | except: raise ValueError('Broken regexp: %r' % arg) 141 | 142 | ## loop over all codepoints that we have 143 | for cp in cp_names: 144 | u = unichr(int(cp, 16)) 145 | name = '-' 146 | name = cp_names[cp] 147 | if r_search.search(name): 148 | yield about(u, cp, name) 149 | 150 | 151 | def u(jenni, input): 152 | '''Look up unicode information.''' 153 | arg = input.bytes[3:] 154 | # jenni.msg('#inamidst', '%r' % arg) 155 | if not arg: 156 | return jenni.reply('You gave me zero length input.') 157 | elif not arg.strip(' '): 158 | if len(arg) > 1: return jenni.reply('%s SPACEs (U+0020)' % len(arg)) 159 | return jenni.reply('1 SPACE (U+0020)') 160 | 161 | # @@ space 162 | if set(arg.upper()) - set( 163 | 'ABCDEFGHIJKLMNOPQRSTUVWYXYZ0123456789- .?+*{}[]\\/^$'): 164 | printable = False 165 | elif len(arg) > 1: 166 | printable = True 167 | else: printable = False 168 | 169 | if printable: 170 | extended = False 171 | for c in '.?+*{}[]\\/^$': 172 | if c in arg: 173 | extended = True 174 | break 175 | 176 | ## allow for codepoints as short as 4 and up to 6 177 | ## since the official spec as of Unicode 7.0 only has 178 | ## hexadeciaml numbers with a length no greater than 6 179 | if 4 <= len(arg) <= 6: 180 | try: u = unichr(int(arg, 16)) 181 | except ValueError: pass 182 | else: return jenni.say(about(u)) 183 | 184 | if extended: 185 | # look up a codepoint with regexp 186 | results = list(islice(codepoint_extended(arg), 4)) 187 | for i, result in enumerate(results): 188 | if (i < 2) or ((i == 2) and (len(results) < 4)): 189 | jenni.say(result) 190 | elif (i == 2) and (len(results) > 3): 191 | jenni.say(result + ' [...]') 192 | if not results: 193 | jenni.reply('Sorry, no results') 194 | else: 195 | # look up a codepoint freely 196 | result = codepoint_simple(arg) 197 | if result is not None: 198 | jenni.say(result) 199 | else: jenni.reply('Sorry, no results for %r.' % arg) 200 | else: 201 | text = arg.decode('utf-8') 202 | if len(text) <= 3: 203 | ## look up less than three podecoints 204 | for u in text: 205 | jenni.say(about(u)) 206 | elif len(text) <= 10: 207 | ## look up more than three podecoints 208 | jenni.reply(' '.join('U+%04X' % ord(c) for c in text)) 209 | else: 210 | ## oh baby 211 | jenni.reply('Sorry, your input is too long!') 212 | u.commands = ['u'] 213 | u.example = '.u 203D' 214 | 215 | 216 | def bytes(jenni, input): 217 | '''Show the input as pretty printed bytes.''' 218 | b = input.bytes 219 | jenni.reply('%r' % b[b.find(' ') + 1:]) 220 | bytes.commands = ['bytes'] 221 | bytes.example = '.bytes \xe3\x8b\xa1' 222 | 223 | if __name__ == '__main__': 224 | print __doc__.strip() 225 | -------------------------------------------------------------------------------- /modules/colours.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | colours.py - jenni Colour Module 4 | Copyright 2015, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | colours = { 13 | "00": "white", 14 | "01": "black", 15 | "02": "blue", 16 | "03": "green", 17 | "04": "light red", 18 | "05": "red", 19 | "06": "magenta (purple)", 20 | "07": "orange", 21 | "08": "yellow", 22 | "09": "light green", 23 | "10": "cyan", 24 | "11": "light cyan", 25 | "12": "light blue", 26 | "13": "light magenta (pink)", 27 | "14": "gray", 28 | "15": "light grey", 29 | "16": "unk", 30 | "17": "unk", 31 | "18": "unk", 32 | "19": "unk", 33 | "20": "unk", 34 | "21": "unk", 35 | "22": "unk", 36 | "23": "unk", 37 | "24": "unk", 38 | } 39 | 40 | 41 | def test_colours(jenni, input): 42 | if not input.admin and input.sender.startswith('#'): 43 | return 44 | output = str() 45 | 46 | keys = colours.keys() 47 | keys.sort() 48 | bold_output = str() 49 | for colour in keys: 50 | output += "\x03{0}{1} ({0})\x03, ".format(colour, colours[colour]) 51 | bold_output += "\x02\x03{0}{1} ({0})\x03\x02, ".format(colour, 52 | colours[colour]) 53 | 54 | output = output[:-2] 55 | bold_output = bold_output[:-2] 56 | 57 | jenni.say(output) 58 | jenni.say(bold_output) 59 | test_colours.commands = ['color', 'colour', 'colors', 'colours'] 60 | test_colours.priority = 'high' 61 | 62 | 63 | if __name__ == '__main__': 64 | print __doc__.strip() 65 | -------------------------------------------------------------------------------- /modules/countdown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | countdown.py - jenni Countdown Module 4 | Copyright 2011-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | from datetime import datetime, timedelta 13 | 14 | ## TODO: just scrape, https://www.timeanddate.com/countdown/generic?iso=20170411T070001&p0=1440&msg=DO+SFO2+DOWNTIME&ud=1&font=cursive 15 | 16 | bad_format = "Please use correct format: .countdown 2012 12 21 You can also try: '.nye -5'" 17 | ## 2036 02 07 18 | 19 | def get_output(calculate_date, today, nye): 20 | #ending = "%s %s-%s-%sT%s00Z" 21 | verb = str() 22 | if calculate_date <= today: 23 | diff = today - calculate_date 24 | verb = "since" 25 | # if nye: 26 | # return get_output(calculate_date + timedelta(days=365), today, False) 27 | else: 28 | diff = calculate_date - today 29 | verb = "until" 30 | output = str() 31 | mills = 0 32 | centuries = 0 33 | decades = 0 34 | years = 0 35 | days = abs(diff.days) 36 | 37 | unit = str() 38 | if days > 365250: 39 | mills = diff.days / 365250 40 | days -= mills * 365250 41 | 42 | if mills == 1: unit = "millennium" 43 | else: unit = "millenniums" 44 | if mills: 45 | output += "%s %s, " % (str(mills), unit) 46 | if days > 36525: 47 | centuries = days / 36525 48 | days -= centuries * 36525 49 | 50 | if centuries == 1: unit = "century" 51 | else: unit = "centuries" 52 | if centuries: 53 | output += "%s %s, " % (str(centuries), unit) 54 | if days > 3652: 55 | decades = days / 3652 56 | days -= decades * 3652 57 | 58 | if decades == 1: unit = "decade" 59 | else: unit = "decades" 60 | if decades: 61 | output += "%s %s, " % (str(decades), unit) 62 | if days > 365: 63 | years = days / 365 64 | days -= years * 365 65 | 66 | if years == 1: unit = "year" 67 | else: unit = "years" 68 | if years: 69 | output += "%s %s, " % (str(years), unit) 70 | 71 | if days: 72 | if days == 1: unit = "day" 73 | else: unit = "days" 74 | output += "%s %s, " % (str(days), unit) 75 | 76 | hours = diff.seconds / 3600 77 | if hours: 78 | if hours == 1: unit = "hour" 79 | else: unit = "hours" 80 | output += "%s %s, " % (str(hours), unit) 81 | 82 | minutes = (diff.seconds/60 - hours * 60) 83 | if minutes: 84 | if minutes > 1: unit = "minutes" 85 | elif minutes == 1: unit = "minute" 86 | output += "%s %s, " % (str(minutes), unit) 87 | 88 | seconds = (diff.seconds/60.0 - hours * 60) - (diff.seconds/60 - hours * 60) 89 | seconds *= 60.0 90 | seconds = int(seconds) 91 | if seconds: 92 | if seconds > 1: unit = 'seconds' 93 | elif seconds == 1: unit = 'second' 94 | output += '%s %s, ' % (str(seconds), unit) 95 | 96 | if output and output[0] == "-": 97 | output = output[1:] 98 | 99 | #output += ending % (verb, year.zfill(4), month.zfill(2), day.zfill(2), offset.zfill(2)) 100 | return '%s%s' % (output, verb) 101 | 102 | 103 | def two(inc): 104 | return str(inc).zfill(2) 105 | def three(inc): 106 | return str(inc).zfill(3) 107 | 108 | 109 | def generic_countdown(jenni, input): 110 | """ .countdown - displays a countdown to a given date. """ 111 | ending = "%s %s-%s-%sT%s" 112 | text = input.group(2) 113 | 114 | if text and len(text.split()) >= 3: 115 | text = input.group(2).split() 116 | year = text[0] 117 | month = text[1] 118 | day = text[2] 119 | 120 | if not year.isdigit() and not month.isdigit() and not day.isdigit(): 121 | return jenni.reply('What are you even trying to do?') 122 | 123 | try: 124 | offset = text[3] 125 | except: 126 | offset = 0 127 | else: 128 | if text: 129 | offset = text.split()[0] 130 | else: 131 | offset = 0 132 | year = str(int(datetime.now().year)) 133 | month = '01' 134 | day = '01' 135 | 136 | try: 137 | float(offset) 138 | except: 139 | #return jenni.reply(':-(') 140 | offset = 0 141 | 142 | 143 | if text and len(text) >= 3 and year.isdigit() and month.isdigit() and day.isdigit(): 144 | calculate_date = datetime(int(year), int(month), int(day), 0, 0, 0) 145 | if abs(float(offset)) >= 14: 146 | return jenni.reply('Do you not love me anymore?') 147 | today = datetime.now() + timedelta(hours=float(offset)) 148 | nye = False 149 | elif -14 <= int(offset) <= 14: 150 | if len(input) <= 3: 151 | offset = 0 152 | else: 153 | offset = offset 154 | calculate_date = datetime(int(datetime.now().year), 1, 1, 0, 0, 0) 155 | today = datetime.now() + timedelta(hours=int(offset)) 156 | nye = True 157 | else: 158 | return jenni.say(bad_format) 159 | 160 | output = get_output(calculate_date, today, nye) 161 | 162 | if offset == 0: 163 | off = '00' 164 | else: 165 | if offset[0] == '+' or offset[0] == '-': 166 | offset = offset[1:] 167 | 168 | prefix = str() 169 | if float(offset) >= 0: 170 | prefix = '+' 171 | else: 172 | prefix = '-' 173 | 174 | if float(offset) % 1 == 0: 175 | off = '%s%s00' % (prefix, two(offset)) 176 | else: 177 | parts = str(offset).split('.') 178 | wholenum = parts[0] 179 | first_part = two(wholenum) 180 | second_part = int(float('.%s' % parts[1]) * 60.0) 181 | second_part = two(second_part) 182 | off = '%s%s%s' % (prefix, first_part, second_part) 183 | 184 | 185 | output = ending % (output, two(year), two(month), two(day), off) 186 | jenni.say(output) 187 | generic_countdown.commands = ['countdown', 'cd', 'nye'] 188 | generic_countdown.priority = 'low' 189 | 190 | 191 | if __name__ == '__main__': 192 | print __doc__.strip() 193 | -------------------------------------------------------------------------------- /modules/dinner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | dinner.py - Dinner Module 4 | Copyright 2014 Sujeet Akula (sujeet@freeboson.org) 5 | Copyright 2013 yano (yanovich.net) 6 | Copyright 2013 Unknown 7 | Licensed under the Eiffel Forum License 2. 8 | 9 | More info: 10 | * jenni-misc: https://github.com/freeboson/jenni-misc/ 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | ''' 14 | 15 | import re 16 | import web 17 | from modules.url import short 18 | 19 | re_mark = re.compile('
(.*?)
') 20 | 21 | def fucking_dinner(jenni, input): 22 | '''.fd -- provide suggestions for dinner''' 23 | txt = input.group(2) 24 | url = 'http://www.whatthefuckshouldimakefordinner.com' 25 | if txt == '-v': 26 | url = 'http://whatthefuckshouldimakefordinner.com/veg.php' 27 | page = web.get(url) 28 | 29 | results = re_mark.findall(page) 30 | 31 | if results: 32 | 33 | dish = results[0][1].upper() 34 | long_url = results[0][0] 35 | 36 | try: 37 | short_url = short(long_url)[0][1] 38 | except: 39 | short_url = long_url 40 | 41 | jenni.say("WHY DON'T YOU EAT SOME FUCKING: " + dish + 42 | " HERE IS THE RECIPE: " + short_url) 43 | 44 | else: 45 | jenni.say("I DON'T FUCKING KNOW, EAT PIZZA.") 46 | 47 | fucking_dinner.commands = ['fucking_dinner', 'fd', 'wtfsimfd'] 48 | fucking_dinner.priority = 'low' 49 | 50 | if __name__ == '__main__': 51 | print __doc__.strip() 52 | -------------------------------------------------------------------------------- /modules/etymology.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | etymology.py - jenni Etymology Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2007-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re 14 | import web 15 | from tools import deprecated 16 | 17 | etyuri = 'http://etymonline.com/?term=%s' 18 | etysearch = 'http://etymonline.com/?search=%s' 19 | 20 | r_definition = re.compile(r'(?ims)]*>.*?') 21 | r_tag = re.compile(r'<(?!!)[^>]+>') 22 | r_whitespace = re.compile(r'[\t\r\n ]+') 23 | 24 | abbrs = [ 25 | 'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp', 26 | 'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk', 27 | '19c', '18c', '17c', '16c', 'St', 'Capt', 'obs', 'Jan', 'Feb', 'Mar', 28 | 'Apr', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'c', 'tr', 'e', 'g' 29 | ] 30 | t_sentence = r'^.*?(?') 35 | s = s.replace('<', '<') 36 | s = s.replace('&', '&') 37 | return s 38 | 39 | def text(html): 40 | html = r_tag.sub('', html) 41 | html = r_whitespace.sub(' ', html) 42 | return unescape(html).strip() 43 | 44 | def etymology(word): 45 | # @@ sbp, would it be possible to have a flag for .ety to get 2nd/etc 46 | # entries? - http://swhack.com/logs/2006-07-19#T15-05-29 47 | 48 | if len(word) > 25: 49 | raise ValueError("Word too long: %s[...]" % word[:10]) 50 | word = {'axe': 'ax/axe'}.get(word, word) 51 | 52 | bytes = web.get(etyuri % word) 53 | definitions = r_definition.findall(bytes) 54 | 55 | if not definitions: 56 | return None 57 | 58 | defn = text(definitions[0]) 59 | m = r_sentence.match(defn) 60 | if not m: 61 | return None 62 | sentence = m.group(0) 63 | 64 | try: 65 | sentence = unicode(sentence, 'iso-8859-1') 66 | sentence = sentence.encode('utf-8') 67 | except: pass 68 | 69 | maxlength = 275 70 | if len(sentence) > maxlength: 71 | sentence = sentence[:maxlength] 72 | words = sentence[:-5].split(' ') 73 | words.pop() 74 | sentence = ' '.join(words) + ' [...]' 75 | 76 | sentence = '"' + sentence.replace('"', "'") + '"' 77 | return sentence + ' - ' + (etyuri % word) 78 | 79 | @deprecated 80 | def f_etymology(self, origin, match, args): 81 | word = match.group(2) 82 | 83 | try: result = etymology(word.encode('utf-8')) 84 | except IOError: 85 | msg = "Can't connect to etymonline.com (%s)" % (etyuri % word) 86 | self.msg(origin.sender, msg) 87 | return 88 | except AttributeError: 89 | result = None 90 | 91 | if result is not None: 92 | self.msg(origin.sender, result) 93 | else: 94 | uri = etysearch % word 95 | msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri) 96 | self.msg(origin.sender, msg) 97 | # @@ Cf. http://swhack.com/logs/2006-01-04#T01-50-22 98 | f_etymology.rule = (['ety'], r"([A-Za-z0-9' .-]+)$") 99 | f_etymology.thread = True 100 | f_etymology.priority = 'high' 101 | 102 | if __name__=="__main__": 103 | import sys 104 | print etymology(sys.argv[1]) 105 | -------------------------------------------------------------------------------- /modules/excuses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | excuses.py - jenni Excuse Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import re 13 | from modules import proxy 14 | import web 15 | 16 | 17 | def excuse(jenni, input): 18 | a = re.compile('(.*)') 19 | 20 | try: 21 | page = proxy.get('http://programmingexcuses.com/') 22 | except: 23 | return jenni.say("I'm all out of excuses!") 24 | 25 | results = a.findall(page) 26 | 27 | if results: 28 | result = results[0] 29 | result = result.strip() 30 | if result[-1] not in ['.', '?', '!']: 31 | result += '.' 32 | jenni.say(result) 33 | else: 34 | jenni.say("I'm too lazy to find an excuse.") 35 | excuse.commands = ['excuse', 'excuses'] 36 | 37 | 38 | if __name__ == '__main__': 39 | print __doc__.strip() 40 | -------------------------------------------------------------------------------- /modules/find.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | find.py - jenni Spell Checking Module 4 | Copyright 2011-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | 11 | Contributions from: Matt Meinwald and Morgan Goose 12 | This module will fix spelling errors if someone corrects them 13 | using the sed notation (s///) commonly found in vi/vim. 14 | """ 15 | 16 | from modules import unicode as uc 17 | import os, re 18 | 19 | 20 | def load_db(): 21 | """ load lines from find.txt to search_dict """ 22 | if not os.path.isfile("find.txt"): 23 | f = open("find.txt", "w") 24 | f.write("#test,yano,foobar\n") 25 | f.close() 26 | search_file = open("find.txt", "r") 27 | lines = search_file.readlines() 28 | search_file.close() 29 | search_dict = dict() 30 | for line in lines: 31 | line = uc.decode(line) 32 | line = uc.encode(line) 33 | a = line.replace(r'\n', '') 34 | new = a.split(r',') 35 | if len(new) < 3: continue 36 | try: 37 | channel = uc.encode(new[0]) 38 | except: 39 | channel = uc.decode(new[0]) 40 | nick = new[1] 41 | if len(new) < 2: continue 42 | if channel not in search_dict: 43 | search_dict[channel] = dict() 44 | if nick not in search_dict[channel]: 45 | search_dict[channel][nick] = list() 46 | if len(new) > 3: 47 | result = ",".join(new[2:]) 48 | result = result.replace('\n','') 49 | elif len(new) == 3: 50 | result = new[-1] 51 | if len(result) > 0: 52 | result = result[:-1] 53 | if result: 54 | search_dict[channel][nick].append(uc.decode(result)) 55 | return search_dict 56 | 57 | def save_db(search_dict): 58 | """ save search_dict to find.txt """ 59 | search_file = open("find.txt", "w") 60 | for channel in search_dict: 61 | if channel is not "": 62 | for nick in search_dict[channel]: 63 | for line in search_dict[channel][nick]: 64 | channel = uc.decode(channel) 65 | channel_utf = uc.encode(channel) 66 | search_file.write(channel_utf) 67 | search_file.write(",") 68 | nick = uc.encode(nick) 69 | search_file.write(nick) 70 | search_file.write(",") 71 | line_utf = line 72 | if not type(str()) == type(line): 73 | line_utf = uc.encode(line) 74 | search_file.write(line_utf) 75 | search_file.write("\n") 76 | search_file.close() 77 | 78 | # Create a temporary log of the most recent thing anyone says. 79 | def collectlines(jenni, input): 80 | """Creates a temporary storage of most recent lines for s///""" 81 | # don't log things in PM 82 | #channel = (input.sender).encode("utf-8") 83 | channel = uc.decode(input.sender) 84 | channel = uc.encode(channel) 85 | nick = (input.nick).encode("utf-8") 86 | if not channel.startswith('#'): return 87 | search_dict = load_db() 88 | if channel not in search_dict: 89 | search_dict[channel] = dict() 90 | if nick not in search_dict[channel]: 91 | search_dict[channel][nick] = list() 92 | templist = search_dict[channel][nick] 93 | line = input.group() 94 | if line.startswith("s/"): 95 | return 96 | elif line.startswith("\x01ACTION"): 97 | line = line[:-1] 98 | templist.append(line) 99 | else: 100 | templist.append(line) 101 | del templist[:-10] 102 | search_dict[channel][nick] = templist 103 | save_db(search_dict) 104 | collectlines.rule = r'.*' 105 | collectlines.priority = 'low' 106 | 107 | def findandreplace(jenni, input): 108 | """s/old text/new text/ -- allows you to replace text from something you previously said""" 109 | # don't bother in PM 110 | channel = (input.sender).encode("utf-8") 111 | nick = (input.nick).encode("utf-8") 112 | 113 | if not channel.startswith('#'): return 114 | 115 | search_dict = load_db() 116 | 117 | rnick = input.group(1) or nick # Correcting other person vs self. 118 | 119 | # only do something if there is conversation to work with 120 | if channel not in search_dict or rnick not in search_dict[channel]: return 121 | 122 | sep = input.group(2) 123 | rest = input.group(3).split(sep) 124 | me = False # /me command 125 | flags = '' 126 | if len(rest) < 2: 127 | return # need at least a find and replacement value 128 | elif len(rest) > 2: 129 | # Word characters immediately after the second separator 130 | # are considered flags (only g and i now have meaning) 131 | flags = re.match(r'\w*',rest[2], re.U).group(0) 132 | #else (len == 2) do nothing special 133 | 134 | count = 'g' in flags and -1 or 1 # Replace unlimited times if /g, else once 135 | if 'i' in flags: 136 | regex = re.compile(re.escape(rest[0]),re.U|re.I) 137 | repl = lambda s: re.sub(regex,rest[1],s,count == 1) 138 | else: 139 | repl = lambda s: s.replace(rest[0],rest[1],count) 140 | 141 | for line in reversed(search_dict[channel][rnick]): 142 | if line.startswith("\x01ACTION"): 143 | me = True # /me command 144 | line = line[8:] 145 | else: 146 | me = False 147 | new_phrase = repl(line) 148 | if new_phrase != line: # we are done 149 | break 150 | 151 | if not new_phrase or new_phrase == line: return # Didn't find anything 152 | 153 | if len(new_phrase) > 512: 154 | new_phrase = new_phrase[:512] 155 | 156 | # Save the new "edited" message. 157 | templist = search_dict[channel][rnick] 158 | templist.append((me and '\x01ACTION ' or '') + new_phrase) 159 | search_dict[channel][rnick] = templist 160 | save_db(search_dict) 161 | 162 | # output 163 | phrase = nick + (input.group(1) and ' thinks ' + rnick or '') + (me and ' ' or " \x02meant\x02 to say: ") + new_phrase 164 | if me and not input.group(1): phrase = '\x02' + phrase + '\x02' 165 | jenni.say(phrase) 166 | 167 | # Matches optional whitespace + 's' + optional whitespace + separator character. 168 | # The separator can be any non-alphanumeric character except for '_', '.', ':' and '-'. 169 | findandreplace.rule = r'(?iu)(?:([^\s:,]+)[\s:,])?\s*s\s*([^\s\w.:-])(.*)' # May work for both this and "meant" (requires input.group(i+1)) 170 | findandreplace.priority = 'high' 171 | 172 | 173 | if __name__ == '__main__': 174 | print __doc__.strip() 175 | -------------------------------------------------------------------------------- /modules/food.py: -------------------------------------------------------------------------------- 1 | #1/usr/bin/env/python 2 | """ 3 | food.py - Jenni Food Module 4 | by afuhrtrumpet 5 | 6 | More info: 7 | * jenni: https://github.com/myano/jenni/ 8 | * Phenny: http://inamidst.com/phenny/ 9 | """ 10 | 11 | from yelpapi import YelpAPI 12 | import random 13 | 14 | 15 | def food(jenni, input): 16 | if not hasattr(jenni.config, 'yelp_api_credentials'): 17 | return 18 | yelp_api = YelpAPI(jenni.config.yelp_api_credentials['consumer_key'], jenni.config.yelp_api_credentials['consumer_secret'], jenni.config.yelp_api_credentials['token'], jenni.config.yelp_api_credentials['token_secret']) 19 | 20 | location = input.group(2) 21 | 22 | if not location: 23 | jenni.say("Please enter a location.") 24 | return 25 | 26 | done = False 27 | max_offset = 5 28 | 29 | try: 30 | while not done: 31 | offset = random.randint(0, max_offset) 32 | response = yelp_api.search_query(category_filter="restaurants", location=location, limit=20, offset=offset) 33 | if len(response['businesses']) > 0: 34 | done = True 35 | jenni.say("How about, " + response['businesses'][random.randint(0, len(response['businesses']) - 1)]['name'] + "?") 36 | else: 37 | max_offset = offset - 1 38 | except YelpAPI.YelpAPIError: 39 | jenni.say("Invalid location!") 40 | 41 | food.commands = ["food"] 42 | food.priority = 'medium' 43 | food.example = '.food ' 44 | 45 | if __name__ == '__main__': 46 | print __doc__.strip() 47 | -------------------------------------------------------------------------------- /modules/freenode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | freenode.py - jenni freenode Specific Stuff 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | def replaced(jenni, input): 14 | command = input.group(1) 15 | responses = { 16 | 'cp': '.cp has been replaced by .u', 17 | 'pc': '.pc has been replaced by .u', 18 | 'unicode': '.unicode has been replaced by .u', 19 | 'compare': '.compare has been replaced by .gcs (googlecounts)', 20 | 'map': 'the .map command has been removed; ask sbp for details', 21 | 'acronym': 'the .acronym command has been removed; ask sbp for details', 22 | # 'img': 'the .img command has been removed; ask sbp for details', 23 | 'v': '.v has been replaced by .val', 24 | 'validate': '.validate has been replaced by .validate', 25 | # 'rates': "moon wanter. moOOoon wanter!", 26 | 'web': 'the .web command has been removed; ask sbp for details', 27 | 'origin': ".origin hasn't been ported to my new codebase yet" 28 | # 'gs': 'sorry, .gs no longer works' 29 | } 30 | try: response = responses[command] 31 | except KeyError: return 32 | else: jenni.reply(response) 33 | replaced.commands = [ 34 | 'cp', 'pc', 'unicode', 'compare', 'map', 'acronym', 35 | 'v', 'validate', 'thesaurus', 'web', 'mangle', 'origin', 36 | 'swhack' 37 | ] 38 | replaced.priority = 'low' 39 | 40 | if __name__ == '__main__': 41 | print __doc__.strip() 42 | -------------------------------------------------------------------------------- /modules/ham.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim: set fileencoding=UTF-8 : 4 | """ 5 | ham.py - jenni Ham Radio Module 6 | Copyright 2011-2013, yano (yanovich.net) 7 | Licensed under the Eiffel Forum License 2. 8 | 9 | More info: 10 | * jenni: https://github.com/myano/jenni/ 11 | * Phenny: http://inamidst.com/phenny/ 12 | 13 | This contains a collection of lookups and calls for ham radio enthusiasts. 14 | """ 15 | 16 | from modules import unicode as uc 17 | import re 18 | import web 19 | 20 | re_look = re.compile('(?i)(.*?)
(.*)') 21 | re_more = re.compile('(?i)(.*?)
\n(.*)') 22 | re_tag = re.compile(r'<[^>]+>') 23 | 24 | morse = { 25 | "a": ".-", 26 | "b": "-...", 27 | "c": "-.-.", 28 | "d": "-..", 29 | "e": ".", 30 | "f": "..-.", 31 | "g": "--.", 32 | "h": "....", 33 | "i": "..", 34 | "j": ".---", 35 | "k": "-.-", 36 | "l": ".-..", 37 | "m": "--", 38 | "n": "-.", 39 | "o": "---", 40 | "p": ".--.", 41 | "q": "--.-", 42 | "r": ".-.", 43 | "s": "...", 44 | "t": "-", 45 | "u": "..-", 46 | "v": "...-", 47 | "w": ".--", 48 | "x": "-..-", 49 | "y": "-.--", 50 | "z": "--..", 51 | "1": ".----", 52 | "2": "..---", 53 | "3": "...--", 54 | "4": "....-", 55 | "5": ".....", 56 | "6": "-....", 57 | "7": "--...", 58 | "8": "---..", 59 | "9": "----.", 60 | "0": "-----", 61 | " ": " ", 62 | ".": ".-.-.-", 63 | ",": "--..--", 64 | "?": "..--..", 65 | "'": ".----.", 66 | "!": "-.-.--", 67 | "/": "-..-.", 68 | "(": "-.--.", 69 | ")": "-.--.-", 70 | "&": ".-...", 71 | ":": "---...", 72 | ";": "-.-.-.", 73 | "=": "-...-", 74 | "+": ".-.-.", 75 | "-": "-....-", 76 | "_": "..--.-", 77 | '"': ".-..-.", 78 | "$": "...-..-", 79 | "@": ".--.-.", 80 | u"ä": ".-.-", 81 | u"æ": ".-.-", 82 | u"ą": ".-.-", 83 | u"à": ".--.-", 84 | u"å": ".--.-", 85 | u"ç": "-.-..", 86 | u"ĉ": "-.-..", 87 | u"ć": "-.-..", 88 | u"š": "----", 89 | u"ð": "..--.", 90 | u"ś": "...-...", 91 | u"è": ".-..-", 92 | u"é": "..-..", 93 | u"đ": "..-..", 94 | u"ę": "..-..", 95 | u"ĝ": "--.-.", 96 | u"ĥ": "----", 97 | u"ĵ": ".---.", 98 | u"ź": "--..-.", 99 | u"ñ": "--.--", 100 | u"ń": "--.--", 101 | u"ö": "---.", 102 | u"ø": "---.", 103 | u"ó": "---.", 104 | u"ŝ": "...-.", 105 | u"þ": ".--..", 106 | u"ü": "..--", 107 | u"ŭ": "..--", 108 | u"ż": "--..-", 109 | } 110 | 111 | def reverse_lookup(v, d=morse): 112 | result = " " 113 | for k in d: 114 | if d[k] == v: 115 | result = k 116 | return result 117 | 118 | def cs(jenni, input): 119 | '''.cs -- queries qth.com for call sign information''' 120 | cs = input.group(2).upper() 121 | try: 122 | link = "http://www.qth.com/callsign.php?cs=" + uc.decode(web.quote(cs)) 123 | except Exception, e: 124 | print e 125 | return jenni.say('Failed to obtain data from qth.com') 126 | page = web.get(link) 127 | info = re_look.findall(page) 128 | more_info = re_more.findall(page) 129 | if info and more_info: 130 | info = info[0] 131 | name = info[0] 132 | name = re_tag.sub(' ', info[0]).strip() 133 | address = info[1].split('
') 134 | address = ', '.join(address[1:]) 135 | address = address.strip() 136 | extra = dict() 137 | for each in more_info: 138 | extra[each[0].strip()] = re_tag.sub('', each[1].strip()).strip() 139 | response = '(%s) ' % (web.quote(cs)) 140 | response += 'Name: %s, Address: %s. ' # More information is available at: %s' 141 | response = response % (uc.decode(name), uc.decode(address)) 142 | for each in more_info: 143 | temp = re_tag.sub('', each[1].strip()) 144 | if not temp: 145 | temp = 'N/A' 146 | response += '%s: %s. ' % (each[0].strip(), temp) 147 | response += 'More information is available at: %s' % (link) 148 | else: 149 | response = 'No matches found.' 150 | jenni.say(response) 151 | cs.commands = ['cs'] 152 | cs.example = '.cs W8LT' 153 | 154 | def cw(jenni, input): 155 | re_cw = re.compile("[/\.\- ]+") 156 | re_noncw = re.compile("[^/\.\- ]+") 157 | if not input.group(2): 158 | return jenni.say('Please provide some input.') 159 | text = input.group(2).lower().rstrip().lstrip() 160 | temp = text.split(" ") 161 | output = str() 162 | if re_cw.findall(text) and not re_noncw.findall(text): 163 | ## MORSE 164 | output = output.replace(' / ', ' ') 165 | for code in temp: 166 | if " " in code: 167 | output += " " 168 | code = code.replace(" ", "") 169 | output += reverse_lookup(code) 170 | output = output.replace(' ', ' ') 171 | output = output.upper() 172 | else: 173 | ## TEXT 174 | for char in text: 175 | try: 176 | output += morse[char] 177 | except KeyError: 178 | output = "Non morse code character used." 179 | break 180 | output += " " 181 | jenni.reply(output) 182 | cw.commands = ['cw'] 183 | cw.thread = True 184 | 185 | if __name__ == '__main__': 186 | print __doc__.strip() 187 | 188 | -------------------------------------------------------------------------------- /modules/head.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | head.py - jenni HTTP Metadata Utilities 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import httplib, time 14 | from htmlentitydefs import name2codepoint 15 | from modules import proxy 16 | import web 17 | 18 | 19 | def head(jenni, input): 20 | """Provide HTTP HEAD information.""" 21 | uri = input.group(2) 22 | uri = (uri or '').encode('utf-8') 23 | if ' ' in uri: 24 | uri, header = uri.rsplit(' ', 1) 25 | else: uri, header = uri, None 26 | 27 | if not uri and hasattr(jenni, 'last_seen_uri'): 28 | try: uri = jenni.last_seen_uri[input.sender] 29 | except KeyError: return jenni.say('?') 30 | 31 | if not uri.startswith('htt'): 32 | uri = 'http://' + uri 33 | 34 | if '/#!' in uri: 35 | uri = uri.replace('/#!', '/?_escaped_fragment_=') 36 | 37 | try: info = proxy.head(uri) 38 | except IOError: return jenni.say("Can't connect to %s" % uri) 39 | except httplib.InvalidURL: return jenni.say("Not a valid URI, sorry.") 40 | 41 | if not isinstance(info, list): 42 | try: info = dict(info) 43 | except TypeError: 44 | return jenni.reply('Try .head http://example.org/ [optional header]') 45 | info['Status'] = '200' 46 | else: 47 | newInfo = dict(info[0]) 48 | newInfo['Status'] = str(info[1]) 49 | info = newInfo 50 | 51 | if header is None: 52 | data = [] 53 | if info.has_key('Status'): 54 | data.append(info['Status']) 55 | if info.has_key('content-type'): 56 | data.append(info['content-type'].replace('; charset=', ', ')) 57 | if info.has_key('last-modified'): 58 | modified = info['last-modified'] 59 | modified = time.strptime(modified, '%a, %d %b %Y %H:%M:%S %Z') 60 | data.append(time.strftime('%Y-%m-%d %H:%M:%S UTC', modified)) 61 | if info.has_key('content-length'): 62 | data.append(info['content-length'] + ' bytes') 63 | jenni.reply(', '.join(data)) 64 | else: 65 | headerlower = header.lower() 66 | if info.has_key(headerlower): 67 | jenni.say(header + ': ' + info.get(headerlower)) 68 | else: 69 | msg = 'There was no %s header in the response.' % header 70 | jenni.say(msg) 71 | head.commands = ['head'] 72 | head.example = '.head http://www.w3.org/' 73 | 74 | if __name__ == '__main__': 75 | print __doc__.strip() 76 | -------------------------------------------------------------------------------- /modules/hindiety.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | hindiety.py - jenni Hindi Etymology Module 5 | Copyright 2012-2013, Kenneth K. Sham and yano (yanovich.net) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re 14 | import web 15 | 16 | url = 'http://www.shabdkosh.com/s?e=%s&t=1' 17 | r_inl = re.compile(r'([^<]+)<') 18 | r_speech = re.compile(r'(?i)h3 class="([^"]+)">\1\ ') 19 | 20 | term = None 21 | speech = None 22 | 23 | 24 | def hindiety(jenni, word): 25 | parts = word.split(' ') 26 | 27 | if len(parts) == 1: 28 | jenni.say('No term given.') 29 | return 30 | 31 | term = parts[1] 32 | speech = None 33 | 34 | if len(parts) == 3: 35 | speech = parts[2] 36 | 37 | bytes = web.get(url % term) 38 | 39 | # Skip to Meanings section 40 | check = '

Meanings

' 41 | index = bytes.find(check) 42 | if index == -1: 43 | jenni.say('Term `%s\' not found.' % term) 44 | return 45 | 46 | bytes = bytes[index + len(check):] 47 | 48 | # From Meanings, skip pass the Hide Transliteration part 49 | check = 'Hide Transliteration)' 50 | index = bytes.find(check) 51 | if index == -1: 52 | jenni.say('ERR 120: Parse error.') 53 | return 54 | 55 | bytes = bytes[index + len(check):] 56 | 57 | # Stop before Synonyms 58 | check = '

Synonyms

' 59 | index = bytes.find(check) 60 | if index == -1: 61 | jenni.say('ERR 121: Parse error.') 62 | return 63 | 64 | bytes = bytes[:index] 65 | 66 | init(jenni, bytes) 67 | 68 | 69 | def init(jenni, bytes): 70 | definitions = bytes.split('class="in l">') 71 | 72 | output = [] 73 | out_hash = {} 74 | t_speech = '' 75 | for line in definitions: 76 | definition = r_inl.findall(line) 77 | 78 | if not definition: 79 | continue 80 | 81 | aline = '==|=='.join(definition) 82 | a_speech = r_speech.findall(aline) 83 | if len(a_speech) > 0: 84 | t_speech = a_speech[0] 85 | 86 | if not t_speech in out_hash: 87 | out_hash[t_speech] = 0 88 | # do not print more than 5 in 89 | # any speech category 90 | if out_hash[t_speech] == 5: 91 | continue 92 | if not speech is None: 93 | if speech.lower() != t_speech.lower(): 94 | continue 95 | 96 | if definition[0].startswith('div'): 97 | continue 98 | 99 | definition_term = definition[0] 100 | definition_term = definition_term.decode('utf-8') 101 | 102 | definition_latin = None 103 | 104 | if len(definition) >= 4: 105 | if definition[2] == 'span class="latin">': 106 | definition_latin = definition[3] 107 | definition_latin = definition_latin[3:] 108 | definition_latin = definition_latin.decode('utf-8') 109 | 110 | if definition_latin is None: 111 | output.append(u'\x02%s:\x02 %s' % (t_speech, definition_term)) 112 | else: 113 | output.append(u'\x02%s:\x02 %s (%s)' % \ 114 | (t_speech, definition_term, definition_latin)) 115 | 116 | out_hash[t_speech] = out_hash[t_speech] + 1 117 | 118 | if output == []: 119 | jenni.say('Speech `%s\' not found for term `%s\'' % \ 120 | (speech, term)) 121 | return 122 | 123 | current_line = '' 124 | for oute in output: 125 | if len(current_line + ', ' + oute) > 200: 126 | jenni.say(current_line) 127 | current_line = oute 128 | continue 129 | if current_line == '': 130 | current_line = oute 131 | continue 132 | current_line += ', ' + oute 133 | if current_line != '': 134 | jenni.say(current_line) 135 | 136 | hindiety.commands = ['hi'] 137 | hindiety.priority = 'low' 138 | 139 | if __name__ == '__main__': 140 | print __doc__.strip() 141 | -------------------------------------------------------------------------------- /modules/hugs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | hugs.py - jenni Hugs Module 4 | Copyright 2015, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | 13 | def hugs(jenni, input): 14 | '''.hugs -- have jenni hug somebody''' 15 | txt = input.group(2) 16 | if not txt: 17 | msg = '\x01ACTION hugs %s\x01' % (input.nick) 18 | return jenni.msg(input.sender, msg, x=True) 19 | parts = txt.split() 20 | to = parts[0] 21 | if to == jenni.config.nick: 22 | to = 'themself' 23 | 24 | msg = '\x01ACTION hugs %s\x01' % (to) 25 | jenni.msg(input.sender, msg, x=True) 26 | hugs.commands = ['hug', 'hugs'] 27 | 28 | if __name__ == '__main__': 29 | print __doc__.strip() 30 | -------------------------------------------------------------------------------- /modules/image_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | image_me.py - jenni Image Fetcher Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | Developed by kaneda (https://josh.myhugesite.com / https://github.com/kaneda) 9 | 10 | More info: 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | """ 14 | 15 | import random 16 | import re 17 | import traceback 18 | import urllib 19 | import urlparse 20 | from modules import proxy 21 | 22 | try: 23 | from BeautifulSoup import BeautifulSoup as Soup 24 | except ImportError: 25 | raise ImportError("Could not find BeautifulSoup library," 26 | "please install to use the image_me module") 27 | 28 | google_images_uri = 'https://www.google.com/search?safe=off' 29 | google_images_uri += '&source=lnms&tbm=isch&q=%s' 30 | 31 | 32 | def image_me(term): 33 | global google_images_uri 34 | 35 | t = urllib.quote_plus(term) 36 | # URL encode the term given 37 | if '%' in term: 38 | t = urllib.quote_plus(term.replace('%', '')) 39 | 40 | content = proxy.get(google_images_uri % t) 41 | 42 | soup = Soup(content) 43 | img_links = [a['href'] for a in soup.findAll('a', 'rg_l', href=True)] 44 | 45 | if img_links: 46 | full_link = img_links[random.randint(0, len(img_links) - 1)] 47 | parsed_link = urlparse.urlparse(full_link) 48 | query = urlparse.parse_qs(parsed_link.query) 49 | img_url = query['imgurl'] 50 | if type(img_url) == list: 51 | img_url = img_url[0] 52 | return urllib.unquote_plus(img_url) 53 | 54 | 55 | def img(jenni, input): 56 | origterm = input.groups()[1] 57 | if not origterm: 58 | return jenni.say('Perhaps you meant ".image_me pugs"?') 59 | origterm = origterm.encode('utf-8') 60 | origterm = origterm.strip() 61 | 62 | error = None 63 | 64 | try: 65 | result = image_me(origterm) 66 | except IOError: 67 | error = "An error occurred connecting to Google Images" 68 | traceback.print_exc() 69 | except Exception as e: 70 | error = "An unknown error occurred: " + str(e) 71 | traceback.print_exc() 72 | 73 | if error is not None: 74 | jenni.say(error) 75 | elif result is not None: 76 | jenni.say(result) 77 | else: 78 | jenni.say('Can\'t find anything in Google Images for "%s".' % origterm) 79 | 80 | img.commands = ['img_me', 'image_me'] 81 | img.priority = 'high' 82 | img.rate = 10 83 | 84 | if __name__ == '__main__': 85 | print __doc__.strip() 86 | -------------------------------------------------------------------------------- /modules/imdb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | ''' 3 | imdb.py - jenni Movie Information Module 4 | Copyright 2014-2015, yano, yanovich.net 5 | Copyright 2012, Elad Alfassa, 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | This module relies on omdbapi.com 9 | 10 | More info: 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | ''' 14 | 15 | from modules import proxy 16 | import json 17 | import re 18 | import urllib2 19 | 20 | API_BASE_URL = 'http://www.omdbapi.com/' 21 | 22 | 23 | def prep_title(txt): 24 | txt = txt.replace(' ', '+') 25 | txt = (txt).encode('utf-8') 26 | txt = urllib2.quote(txt) 27 | return txt 28 | 29 | 30 | def movie(jenni, input): 31 | '''.imdb movie/show title -- displays information about a production''' 32 | 33 | if not input.group(2): 34 | return jenni.say('Please enter a movie or TV show title. ' 35 | 'Year is optional.') 36 | 37 | word = input.group(2).rstrip() 38 | matchObj = re.match(r'([\w\s]*)\s?,\s?(\d{4})', word, re.M | re.I) 39 | 40 | if matchObj: 41 | title = matchObj.group(1) 42 | year = matchObj.group(2) 43 | title = prep_title(title) 44 | uri = API_BASE_URL + '?t=%s&y=%s&plot=short&r=json' % (title, year) 45 | else: 46 | title = word 47 | title = prep_title(title) 48 | uri = API_BASE_URL + '?t=%s&plot=short&r=json' % (title) 49 | 50 | try: 51 | page = proxy.get(uri) 52 | except: 53 | return jenni.say('[IMDB] Connection to API did not succeed.') 54 | 55 | try: 56 | data = json.loads(page) 57 | except: 58 | return jenni.say("[IMDB] Couldn't make sense of information from API") 59 | 60 | message = '[IMDB] ' 61 | 62 | if data['Response'] == 'False': 63 | if 'Error' in data: 64 | message += data['Error'] 65 | else: 66 | message += 'Got an error from imdbapi' 67 | else: 68 | pre_plot_output = u'Title: {0} | Released: {1} | Rated: {2} ' 69 | pre_plot_output += '| Rating: {3} | Metascore: {4} | Genre: {5} ' 70 | pre_plot_output += '| Runtime: {6} | Plot: ' 71 | genre = data['Genre'] 72 | runtime = data['Runtime'] 73 | pre_plot = pre_plot_output.format(data['Title'], data['Released'], 74 | data['Rated'], data['imdbRating'], 75 | data['Metascore'], genre, 76 | runtime) 77 | 78 | after_plot_output = ' | IMDB Link: http://imdb.com/title/{0}' 79 | after_plot = after_plot_output.format(data['imdbID']) 80 | truncation = '[...]' 81 | 82 | ## 510 - (16 + 8 + 63) 83 | ## max_chars (minus \r\n) - (max_nick_length + max_ident_length 84 | ## + max_vhost_lenth_on_freenode) 85 | max_len_of_plot = 423 - (len(pre_plot) + len(after_plot) + len(truncation)) 86 | 87 | new_plot = data['Plot'] 88 | if len(data['Plot']) > max_len_of_plot: 89 | new_plot = data['Plot'][:max_len_of_plot] + truncation 90 | 91 | message = pre_plot + new_plot + after_plot 92 | 93 | jenni.say(message) 94 | movie.commands = ['imdb', 'movie', 'movies', 'show', 'tv', 'television'] 95 | movie.example = '.imdb Movie Title, 2015' 96 | 97 | if __name__ == '__main__': 98 | print __doc__.strip() 99 | -------------------------------------------------------------------------------- /modules/info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | info.py - jenni Information Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | from itertools import izip_longest 14 | 15 | def fchannels(): 16 | try: 17 | f = open("nochannels.txt", "r") 18 | except: 19 | return False 20 | lines = f.readlines()[0] 21 | f.close() 22 | lines = lines.replace('\n', '') 23 | return lines.split(',') 24 | 25 | 26 | def doc(jenni, input): 27 | """Shows a command's documentation, and possibly an example.""" 28 | if input.group(1) == "help": 29 | name = input.group(2) 30 | else: 31 | name = input.group(1) 32 | name = name.lower() 33 | 34 | if name in jenni.doc: 35 | jenni.reply(jenni.doc[name][0]) 36 | if jenni.doc[name][1]: 37 | jenni.say('e.g. ' + jenni.doc[name][1]) 38 | doc.rule = '(?i)$nick[,:]\s(?:help|doc) +([A-Za-z]+)(?:\?+)?$' 39 | doc.example = '$nickname: doc tell?' 40 | doc.priority = 'low' 41 | 42 | 43 | def commands(jenni, input): 44 | common_commands = "Common commands: join - Join the provided channel. (admin only); part - Leave the provided channel. (admin only); animate_me, nm8_me - Find an animated gif from giphy.com; py, python - Interpret some Python (runs on Google App Engine); s// - Not a command, jenni will listen for phrases beginning with s/ and perform basic find-replace functionality. Note that this is not PRE compliant; .food - Find food in your area using the Yelp API; img_me, image_me - Provide a random result from the first page of Google Image search; commands, help - Display a list of all commands, ip, iplookup, host - Get approximate geolocation from an IP or host using freegeoip.net; mustache_me - Adds a mustache to the image returned for the given search term; reload : Ask jenni to reload a module. (admin only), bing - Provides the first result from a Bing search; duck, ddg - Provides the first link from a DuckDuckGo search; g - - Google for and return the top result; search - Provides the first result from Bing, DuckDuckgo, and Google; tell, to - relays a message to a person the next time they say something anywhere jenni is present; w, wik, wiki - Returns the wiki entry for ; xkcd - Randomly generates a valid URL for an xkcd item." 45 | 46 | jenni.reply("I'm sending you a list of my most common commands in private.") 47 | jenni.reply('For a list of all of my commands, please visit: https://is.gd/CPStvK') 48 | 49 | common_split = izip_longest(*[iter(common_commands)]*445, fillvalue='') 50 | for split_commands in common_split: 51 | jenni.msg(input.nick, ''.join(split_commands), False, False, 1) 52 | 53 | commands.commands = ['commands', 'help'] 54 | commands.priority = 'low' 55 | 56 | 57 | def help(jenni, input): 58 | response = ( 59 | 'Hi, I\'m a bot. Say ".commands" to me in private for a list ' + 60 | 'of my commands, or see https://github.com/myano/jenni/wiki for more ' + 61 | 'general details. My owner is %s.' 62 | ) % jenni.config.owner 63 | jenni.reply(response) 64 | help.rule = ('$nick', r'(?i)help(?:[?!]+)?$') 65 | help.priority = 'low' 66 | 67 | 68 | def stats(jenni, input): 69 | """Show information on command usage patterns.""" 70 | if input.sender == '##uno': 71 | return 72 | commands = dict() 73 | users = dict() 74 | channels = dict() 75 | 76 | 77 | ignore = set(['f_note', 'startup', 'message', 'noteuri', 78 | 'say_it', 'collectlines', 'oh_baby', 'chat', 79 | 'collect_links', 'bb_collect', 'random_chat']) 80 | for (name, user), count in jenni.stats.iteritems(): 81 | if name in ignore: 82 | continue 83 | if not user: 84 | continue 85 | 86 | if not user.startswith('#'): 87 | try: 88 | users[user] += count 89 | except KeyError: 90 | users[user] = count 91 | else: 92 | try: 93 | commands[name] += count 94 | except KeyError: 95 | commands[name] = count 96 | 97 | try: 98 | channels[user] += count 99 | except KeyError: 100 | channels[user] = count 101 | 102 | comrank = sorted([(b, a) for (a, b) in commands.iteritems()], reverse=True) 103 | userank = sorted([(b, a) for (a, b) in users.iteritems()], reverse=True) 104 | charank = sorted([(b, a) for (a, b) in channels.iteritems()], reverse=True) 105 | 106 | # most heavily used commands 107 | creply = 'most used commands: ' 108 | for count, command in comrank[:10]: 109 | creply += '%s (%s), ' % (command, count) 110 | jenni.say(creply.rstrip(', ')) 111 | 112 | # most heavy users 113 | reply = 'power users: ' 114 | k = 1 115 | for count, user in userank: 116 | if ' ' not in user and '.' not in user: 117 | reply += '%s (%s), ' % (user, count) 118 | k += 1 119 | if k > 10: 120 | break 121 | jenni.say(reply.rstrip(', ')) 122 | 123 | # most heavy channels 124 | chreply = 'power channels: ' 125 | bchannels = fchannels() 126 | for count, channel in charank[:3]: 127 | if bchannels and channel in bchannels: 128 | continue 129 | chreply += '%s (%s), ' % (channel, count) 130 | jenni.say(chreply.rstrip(', ')) 131 | stats.commands = ['stats'] 132 | stats.priority = 'low' 133 | 134 | if __name__ == '__main__': 135 | print __doc__.strip() 136 | -------------------------------------------------------------------------------- /modules/insult.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | insult.py - Jenni Insult Module 4 | by alekhin0w 5 | 6 | More info: 7 | * Jenni: https://github.com/myano/jenni/ 8 | * Phenny: http://inamidst.com/phenny/ 9 | """ 10 | import os, random 11 | from modules import unicode as uc 12 | 13 | def insult(jenni, input): 14 | """ insults with configured language insult """ 15 | try: 16 | insultFilename = os.path.expanduser('~/.jenni/insult.'+ jenni.config.insult_lang +'.txt') 17 | except: 18 | jenni.say("You need to configure the default language!") 19 | return 20 | 21 | target = input.group(2) 22 | if not target: 23 | return jenni.reply('.i !') 24 | target.encode('utf-8') 25 | target = (target).strip() 26 | try: 27 | fn = open(insultFilename, "r") 28 | except IOError as e: 29 | generateDatabase(jenni, insultFilename) 30 | fn = open(insultFilename, "r") 31 | lines = fn.readlines() 32 | fn.close() 33 | random.seed() 34 | jenni.say(target + ': ' + uc.decode(random.choice(lines))) 35 | 36 | insult.commands = ['i'] 37 | insult.priority = 'medium' 38 | insult.example = '.i ' 39 | 40 | def addinsult(jenni, input): 41 | """.iadd -- adds a harsh adjetive to the insult database""" 42 | try: 43 | insultFilename = os.path.expanduser('~/.jenni/insult.'+ jenni.config.insult_lang +'.txt') 44 | except: 45 | jenni.say("You need to configure the default language!") 46 | return 47 | 48 | text = input.group(2) 49 | text = uc.encode(text) 50 | fn = open(insultFilename, "a") 51 | fn.write(text) 52 | fn.write("\n") 53 | fn.close() 54 | jenni.reply("Insult added.") 55 | addinsult.commands = ['iadd'] 56 | addinsult.priority = 'medium' 57 | addinsult.example = '.iadd Bad Person' 58 | addinsult.rate = 30 59 | 60 | def generateDatabase(jenni, insultFilename): 61 | if jenni.config.insult_lang == "english": 62 | insultList = ['fuck you', 'stupid', 'asshole', 'you suck'] 63 | elif jenni.config.insult_lang == "spanish": 64 | insultList = ['puto', 'trolo', 'forro', 'insurrecto', 'trolita', 'aguafiestas', 'actualizame esta gil', 'apestoso usuario de windows'] 65 | else: 66 | return # silent fail due lack of configuration 67 | fn = open(insultFilename, "a") 68 | 69 | for insult in insultList: 70 | fn.write(insult) 71 | fn.write("\n") 72 | fn.close() 73 | 74 | jenni.say(jenni.config.insult_lang + " insult database created.") 75 | 76 | if __name__ == '__main__': 77 | print __doc__.strip() 78 | -------------------------------------------------------------------------------- /modules/ip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | """ 4 | ip.py - jenni IP Lookup Module 5 | Copyright 2013, yano (yanovich.net) 6 | Copyright 2011, Dimitri Molenaars (TyRope.nl) 7 | Licensed under the Eiffel Forum License 2. 8 | 9 | More info: 10 | * Willie: http://willie.dftba.net 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | 14 | This module has been imported from Willie. 15 | """ 16 | 17 | from modules import unicode as uc 18 | import json 19 | import re 20 | import socket 21 | import web 22 | 23 | base = 'http://freegeoip.net/json/' 24 | re_ip = re.compile('(?i)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') 25 | re_country = re.compile('(?i)(.+), (.+ of)') 26 | 27 | def ip_lookup(jenni, input): 28 | txt = input.group(2) 29 | if not txt: 30 | return jenni.reply("No search term!") 31 | txt = uc.encode(txt) 32 | query = uc.decode(txt) 33 | response = "[IP/Host Lookup] " 34 | try: 35 | page = web.get(base + txt) 36 | except IOError, err: 37 | return jenni.say('Could not access given address. (Detailed error: %s)' % (err)) 38 | try: 39 | results = json.loads(page) 40 | except: 41 | return jenni.reply('Did not receive proper JSON from %s' % (base)) 42 | if results: 43 | if re_ip.findall(query): 44 | ## IP Address 45 | try: 46 | hostname = socket.gethostbyaddr(query)[0] 47 | except: 48 | hostname = 'Unknown Host' 49 | response += 'Hostname: ' + str(hostname) 50 | else: 51 | ## Host name 52 | response += 'IP: ' + results['ip'] 53 | spacing = ' |' 54 | for param in results: 55 | if not results[param]: 56 | results[param] = 'N/A' 57 | if 'city' in results: 58 | response += '%s City: %s' % (spacing, results['city']) 59 | if 'region_name' in results: 60 | response += '%s State: %s' % (spacing, results['region_name']) 61 | if 'country_name' in results: 62 | country = results['country_name'] 63 | match = re_country.match(country) 64 | if match: 65 | country = ' '.join(reversed(match.groups())) 66 | response += '%s Country: %s' % (spacing, country) 67 | if 'zipcode' in results: 68 | response += '%s ZIP: %s' % (spacing, results['zipcode']) 69 | response += '%s Latitude: %s' % (spacing, results['latitude']) 70 | response += '%s Longitude: %s' % (spacing, results['longitude']) 71 | jenni.reply(response) 72 | ip_lookup.commands = ['ip', 'iplookup', 'host'] 73 | ip_lookup.example = ".iplookup 8.8.8.8" 74 | 75 | if __name__ == '__main__': 76 | print __doc__.strip() 77 | -------------------------------------------------------------------------------- /modules/isup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | isup.py - Simple website status check with isup.me 4 | Copyright 2013-2014, yano (yanovich.net) 5 | Copyright 2012-2013 Edward Powell (embolalaia.net) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | This allows users to check if a website is up through isup.me. 9 | 10 | More info: 11 | * Willie: http://willie.dftba.net/ 12 | * jenni: https://github.com/myano/jenni/ 13 | * Phenny: http://inamidst.com/phenny/ 14 | """ 15 | 16 | from modules import proxy 17 | import re 18 | import web 19 | 20 | 21 | def isup(jenni, input): 22 | '''isup.me website status checker''' 23 | site = input.group(2) 24 | if not site: 25 | return jenni.reply('What site do you want to check?') 26 | if ' ' in site: 27 | idx = site.find(' ') 28 | site = site[:idx+1] 29 | site = (site).strip() 30 | 31 | if site[:7] != 'http://' and site[:8] != 'https://': 32 | if '://' in site: 33 | protocol = site.split('://')[0] + '://' 34 | return jenni.reply('Try it again without the %s' % protocol) 35 | else: 36 | site = 'http://' + site 37 | try: 38 | response = proxy.get(site) 39 | except Exception as e: 40 | jenni.say(site + ' looks down from here.') 41 | return 42 | 43 | if response: 44 | jenni.say(site + ' looks fine to me.') 45 | else: 46 | jenni.say(site + ' is down from here.') 47 | isup.commands = ['isup'] 48 | isup.example = '.isup google.com' 49 | 50 | if __name__ == '__main__': 51 | print __doc__.strip() 52 | -------------------------------------------------------------------------------- /modules/latex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set fileencoding=UTF-8 : 3 | """ 4 | latex.py - jenni LaTeX Module 5 | Copyright 2011-2013, yano (yanovich.net) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import web 14 | 15 | HTML_ENCODINGS = { 16 | " ": "%20", 17 | "!": "%21", 18 | '"': "%22", 19 | "#": "%23", 20 | "$": "%24", 21 | "%": "%25", 22 | "&": "%26", 23 | "'": "%27", 24 | "(": "%28", 25 | ")": "%29", 26 | "*": "%2A", 27 | "+": "%2B", 28 | ",": "%2C", 29 | "-": "%2D", 30 | ".": "%2E", 31 | "/": "%2F", 32 | ":": "%3A", 33 | ";": "%3B", 34 | "<": "%3C", 35 | "=": "%3D", 36 | ">": "%3E", 37 | "?": "%3F", 38 | "@": "%40", 39 | "[": "%5B", 40 | "\\": "%5C", 41 | "]": "%5D", 42 | "^": "%5E", 43 | "_": "%5F", 44 | "`": "%60", 45 | "{": "%7B", 46 | "|": "%7C", 47 | "}": "%7D", 48 | "~": "%7E", 49 | "€": "%80", 50 | "‚": "%82", 51 | "ƒ": "%83", 52 | "„": "%84", 53 | "…": "%85", 54 | "†": "%86", 55 | "‡": "%87", 56 | "ˆ": "%88", 57 | "‰": "%89", 58 | "Š": "%8A", 59 | "‹": "%8B", 60 | "Œ": "%8C", 61 | "Ž": "%8E", 62 | "‘": "%91", 63 | "’": "%92", 64 | "“": "%93", 65 | "”": "%94", 66 | "•": "%95", 67 | "–": "%96", 68 | "—": "%97", 69 | "˜": "%98", 70 | "™": "%99", 71 | "š": "%9A", 72 | "›": "%9B", 73 | "œ": "%9C", 74 | "ž": "%9E", 75 | "Ÿ": "%9F", 76 | "¡": "%A1", 77 | "¢": "%A2", 78 | "£": "%A3", 79 | "¥": "%A5", 80 | "|": "%A6", 81 | "§": "%A7", 82 | "¨": "%A8", 83 | "©": "%A9", 84 | "ª": "%AA", 85 | "«": "%AB", 86 | "¬": "%AC", 87 | "¯": "%AD", 88 | "®": "%AE", 89 | "¯": "%AF", 90 | "°": "%B0", 91 | "±": "%B1", 92 | "²": "%B2", 93 | "³": "%B3", 94 | "´": "%B4", 95 | "µ": "%B5", 96 | "¶": "%B6", 97 | "·": "%B7", 98 | "¸": "%B8", 99 | "¹": "%B9", 100 | "º": "%BA", 101 | "»": "%BB", 102 | "¼": "%BC", 103 | "½": "%BD", 104 | "¾": "%BE", 105 | "¿": "%BF", 106 | "À": "%C0", 107 | "Á": "%C1", 108 | "Â": "%C2", 109 | "Ã": "%C3", 110 | "Ä": "%C4", 111 | "Å": "%C5", 112 | "Æ": "%C6", 113 | "Ç": "%C7", 114 | "È": "%C8", 115 | "É": "%C9", 116 | "Ê": "%CA", 117 | "Ë": "%CB", 118 | "Ì": "%CC", 119 | "Í": "%CD", 120 | "Î": "%CE", 121 | "Ï": "%CF", 122 | "Ð": "%D0", 123 | "Ñ": "%D1", 124 | "Ò": "%D2", 125 | "Ó": "%D3", 126 | "Ô": "%D4", 127 | "Õ": "%D5", 128 | "Ö": "%D6", 129 | "Ø": "%D8", 130 | "Ù": "%D9", 131 | "Ú": "%DA", 132 | "Û": "%DB", 133 | "Ü": "%DC", 134 | "Ý": "%DD", 135 | "Þ": "%DE", 136 | "ß": "%DF", 137 | "à": "%E0", 138 | "á": "%E1", 139 | "â": "%E2", 140 | "ã": "%E3", 141 | "ä": "%E4", 142 | "å": "%E5", 143 | "æ": "%E6", 144 | "ç": "%E7", 145 | "è": "%E8", 146 | "é": "%E9", 147 | "ê": "%EA", 148 | "ë": "%EB", 149 | "ì": "%EC", 150 | "í": "%ED", 151 | "î": "%EE", 152 | "ï": "%EF", 153 | "ð": "%F0", 154 | "ñ": "%F1", 155 | "ò": "%F2", 156 | "ó": "%F3", 157 | "ô": "%F4", 158 | "õ": "%F5", 159 | "ö": "%F6", 160 | "÷": "%F7", 161 | "ø": "%F8", 162 | "ù": "%F9", 163 | "ú": "%FA", 164 | "û": "%FB", 165 | "ü": "%FC", 166 | "ý": "%FD", 167 | "þ": "%FE", 168 | "ÿ": "%FF", 169 | } 170 | 171 | uri = "http://www.numberempire.com/equation.render?" 172 | 173 | def latex(jenni, input): 174 | text = input.group(2) 175 | for char in text: 176 | if char in HTML_ENCODINGS: 177 | text = text.replace(char, HTML_ENCODINGS[char]) 178 | bah = uri + text 179 | url = "https://tinyurl.com/api-create.php?url={0}".format(bah) 180 | a = web.get(url) 181 | a = a.replace("http:", "https:") 182 | jenni.reply(a) 183 | latex.commands = ['tex', 'latex'] 184 | latex.priority = 'high' 185 | 186 | if __name__ == '__main__': 187 | print __doc__.strip() 188 | -------------------------------------------------------------------------------- /modules/magic_8_ball.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | magic8ball.py - jenni's Magic 8 Ball Module 4 | Copyright 2015, Kevin Holland (kevinholland94@gmail.com) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | This module is a Magic 8 Ball implementation that will give one answer for a question on a given day 8 | This works by hashing the user's input and seeding it with the current date (as a string). This way, 9 | the same question will always have the same answer on a given day. 10 | 11 | More info: 12 | * jenni: https://github.com/myano/jenni/ 13 | * Phenny: http://inamidst.com/phenny/ 14 | ''' 15 | 16 | import random 17 | import hashlib 18 | from time import time 19 | 20 | random.seed() 21 | 22 | def magic8Ball(jenni, input): 23 | #real yes or no answers 24 | answers = [ 'It is certain', 25 | 'It is decidedly so', 26 | 'Without a doubt', 27 | 'Yes definitely', 28 | 'You may rely on it', 29 | 'As I see it, yes', 30 | 'Most likely', 31 | 'Outlook good', 32 | 'Yes', 33 | 'Signs point to yes', 34 | 'Don\'t count on it', 35 | 'My reply is no', 36 | 'My sources say no', 37 | 'Outlook not so good', 38 | 'Very doubtful' 39 | ] 40 | 41 | #try again answer 42 | askAgainAnswers = [ 'Reply hazy try again', 43 | 'Ask again later', 44 | 'Better not tell you now', 45 | 'Cannot predict now', 46 | 'Concentrate and ask again' 47 | ] 48 | 49 | #1 in 5 chance of getting an ask again answer (randint() is inclusive) 50 | if (random.randint(1,5) == 1): 51 | jenni.reply(random.choice(askAgainAnswers)) 52 | else: #else produce a real answer 53 | hash = hashlib.sha224(input) 54 | hash.update(str(time())) 55 | jenni.reply(answers[int(hash.hexdigest(), 16) % len(answers)]) 56 | 57 | 58 | magic8Ball.commands = ['magic8Ball', 'm8b'] 59 | magic8Ball.priority = 'low' 60 | magic8Ball.example = '.m8b will it rain tomorrow?' 61 | 62 | if __name__ == '__main__': 63 | print __doc__.strip() 64 | -------------------------------------------------------------------------------- /modules/man.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | man.py - jenni man page linker 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | Developed by kaneda (https://jbegleiter.com / https://github.com/kaneda) 9 | 10 | More info: 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | """ 14 | 15 | import re 16 | import traceback 17 | import urllib 18 | import urlparse 19 | 20 | try: 21 | from BeautifulSoup import BeautifulSoup as Soup 22 | except ImportError: 23 | raise ImportError("Could not find BeautifulSoup library," 24 | "please install to use the man module") 25 | 26 | man_uri = "http://man.he.net/?topic=%s§ion=all" 27 | not_found = "No matches for \"%s\"" 28 | 29 | def man(jenni, input): 30 | global man_uri 31 | global not_found 32 | 33 | term = input.groups()[1] 34 | if not term: 35 | return jenni.say('Perhaps you meant ".man ls"?') 36 | 37 | if len(term.split(' ')) > 1: 38 | return jenni.say('I was expecting only one search term') 39 | 40 | t = urllib.quote_plus(term) 41 | n = not_found % term 42 | # URL encode the term given 43 | if '%' in term: 44 | t = urllib.quote_plus(term.replace('%', '')) 45 | n = not found % term.replace('%','') 46 | 47 | content = urllib.urlopen(man_uri % t).read() 48 | soup = Soup(content) 49 | h2s = soup.findAll('h2') 50 | 51 | for h in h2s: 52 | if n in h: 53 | return jenni.say("Couldn't find the man page {0} on man.he.net".format(term)) 54 | 55 | lines = soup.pre.contents[0] 56 | try: 57 | desc_idx = lines.index('DESCRIPTION') 58 | except: 59 | return jenni.say('Could not find a "DESCRIPTION" section. :(') 60 | 61 | try: 62 | desc_end_idx = lines[desc_idx:].index('\n\n') 63 | except: 64 | return jenni.say('Could not find an ending to the "DESCRIPTION" section. :-(') 65 | 66 | description = lines[desc_idx:desc_idx + desc_end_idx] 67 | 68 | jenni.say(man_uri % t) 69 | jenni.say("I am sending you the content of this page in a private message") 70 | description = description.replace('DESCRIPTION\n', 'DESCRIPTION of %s\n' % (term)) 71 | 72 | for c in description.split('\n'): 73 | jenni.msg(input.nick, c, False, False, 1) 74 | 75 | man.commands = ['man', 'manual'] 76 | man.priority = 'high' 77 | man.rate = 10 78 | 79 | if __name__ == '__main__': 80 | print __doc__.strip() 81 | -------------------------------------------------------------------------------- /modules/motivate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | motivate.py - motivate Module 4 | Copyright 2013 yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | ''' 11 | 12 | def motivate(jenni, input): 13 | '''!m -- motivate somebody!''' 14 | if input: 15 | nick = input 16 | nick = (nick[3:]).strip() 17 | jenni.say("You're doing good work, %s!" % (nick)) 18 | motivate.rule = r'(?u)^(\!|\.)m\s+(\S+)' 19 | 20 | if __name__ == '__main__': 21 | print __doc__.strip() 22 | -------------------------------------------------------------------------------- /modules/mustache_me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | mustache_me.py - jenni Mustachifier Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | Developed by kaneda (https://josh.myhugesite.com / https://github.com/kaneda) 9 | 10 | More info: 11 | * jenni: https://github.com/myano/jenni/ 12 | * Phenny: http://inamidst.com/phenny/ 13 | """ 14 | 15 | try: 16 | from modules import image_me 17 | except ImportError: 18 | raise ImportError("You must have the image_me module to use" 19 | "the mustache_me module") 20 | 21 | mustache_uri = 'http://mustachify.me/?src=%s' 22 | 23 | 24 | def mustache_me(term): 25 | quoted_url = image_me.image_me(term) 26 | 27 | global mustache_uri 28 | 29 | if quoted_url: 30 | return (mustache_uri % quoted_url) 31 | 32 | 33 | def mustache(jenni, input): 34 | origterm = input.groups()[1] 35 | if not origterm: 36 | return jenni.say('Perhaps you meant ".mustache_me pugs"?') 37 | origterm = origterm.encode('utf-8') 38 | origterm = origterm.strip() 39 | 40 | error = None 41 | 42 | try: 43 | result = mustache_me(origterm) 44 | except IOError: 45 | error = "An error occurred connecting to Google Images" 46 | error += "or the mustachifier" 47 | traceback.print_exc() 48 | except Exception as e: 49 | error = "An unknown error occurred: " + str(e) 50 | traceback.print_exc() 51 | 52 | if error is not None: 53 | jenni.say(error) 54 | elif result is not None: 55 | jenni.say(result) 56 | else: 57 | jenni.say('Can\'t find anything in Google Images for "%s".' % origterm) 58 | 59 | mustache.commands = ['mustache_me'] 60 | mustache.priority = 'high' 61 | mustache.rate = 10 62 | 63 | if __name__ == '__main__': 64 | print __doc__.strip() 65 | -------------------------------------------------------------------------------- /modules/oblique.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | oblique.py - Web Services Interface 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re, urllib 14 | import web 15 | 16 | definitions = 'https://github.com/myano/jenni/wiki/oblique' 17 | 18 | r_item = re.compile(r'(?i)
  • (.*?)
  • ') 19 | r_tag = re.compile(r'<[^>]+>') 20 | 21 | def mappings(uri): 22 | result = {} 23 | bytes = web.get(uri) 24 | for item in r_item.findall(bytes): 25 | item = r_tag.sub('', item).strip(' \t\r\n') 26 | if not ' ' in item: continue 27 | 28 | command, template = item.split(' ', 1) 29 | if not command.isalnum(): continue 30 | if not template.startswith(('http://', 'https://')): continue 31 | result[command] = template.replace('&', '&') 32 | return result 33 | 34 | def service(jenni, input, command, args): 35 | t = o.services[command] 36 | template = t.replace('${args}', urllib.quote(args.encode('utf-8'), '')) 37 | template = template.replace('${nick}', urllib.quote(input.nick, '')) 38 | uri = template.replace('${sender}', urllib.quote(input.sender, '')) 39 | 40 | info = web.head(uri) 41 | if isinstance(info, list): 42 | info = info[0] 43 | if not 'text/plain' in info.get('content-type', '').lower(): 44 | return jenni.reply("Sorry, the service didn't respond in plain text.") 45 | bytes = web.get(uri) 46 | lines = bytes.splitlines() 47 | if not lines: 48 | return jenni.reply("Sorry, the service didn't respond any output.") 49 | try: line = lines[0].encode('utf-8')[:350] 50 | except: line = lines[0][:250] 51 | jenni.say(line) 52 | 53 | def refresh(jenni): 54 | if hasattr(jenni.config, 'services'): 55 | services = jenni.config.services 56 | else: services = definitions 57 | 58 | old = o.services 59 | o.serviceURI = services 60 | o.services = mappings(o.serviceURI) 61 | return len(o.services), set(o.services) - set(old) 62 | 63 | def o(jenni, input): 64 | """Call a webservice.""" 65 | text = input.group(2) 66 | 67 | if (not o.services) or (text == 'refresh'): 68 | length, added = refresh(jenni) 69 | if text == 'refresh': 70 | msg = 'Okay, found %s services.' % length 71 | if added: 72 | msg += ' Added: ' + ', '.join(sorted(added)[:5]) 73 | if len(added) > 5: msg += ', &c.' 74 | return jenni.reply(msg) 75 | 76 | if not text: 77 | return jenni.reply('Try %s for details.' % o.serviceURI) 78 | 79 | if ' ' in text: 80 | command, args = text.split(' ', 1) 81 | else: command, args = text, '' 82 | command = command.lower() 83 | 84 | if command == 'service': 85 | msg = o.services.get(args, 'No such service!') 86 | return jenni.reply(msg) 87 | 88 | if not o.services.has_key(command): 89 | return jenni.reply('Service not found in %s' % o.serviceURI) 90 | 91 | if hasattr(jenni.config, 'external'): 92 | default = jenni.config.external.get('*') 93 | manifest = jenni.config.external.get(input.sender, default) 94 | if manifest: 95 | commands = set(manifest) 96 | if (command not in commands) and (manifest[0] != '!'): 97 | return jenni.reply('Sorry, %s is not whitelisted' % command) 98 | elif (command in commands) and (manifest[0] == '!'): 99 | return jenni.reply('Sorry, %s is blacklisted' % command) 100 | service(jenni, input, command, args) 101 | o.commands = ['o'] 102 | o.example = '.o servicename arg1 arg2 arg3' 103 | o.services = {} 104 | o.serviceURI = None 105 | o.rate = 20 106 | 107 | def snippet(jenni, input): 108 | if not o.services: 109 | refresh(jenni) 110 | 111 | search = urllib.quote(input.group(2).encode('utf-8')) 112 | py = "BeautifulSoup.BeautifulSoup(re.sub('<.*?>|(?<= ) +', '', " + \ 113 | "''.join(chr(ord(c)) for c in " + \ 114 | "eval(urllib.urlopen('https://ajax.googleapis.com/ajax/serv" + \ 115 | "ices/search/web?v=1.0&q=" + search + "').read()" + \ 116 | ".replace('null', 'None'))['responseData']['resul" + \ 117 | "ts'][0]['content'].decode('unicode-escape')).replace(" + \ 118 | "'"', '\x22')), convertEntities=True)" 119 | service(jenni, input, 'py', py) 120 | snippet.commands = ['snippet'] 121 | snippet.rate = 20 122 | 123 | if __name__ == '__main__': 124 | print __doc__.strip() 125 | -------------------------------------------------------------------------------- /modules/oed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | oed.py - OED Module 4 | Copyright 2014 Sujeet Akula (sujeet@freeboson.org) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni-misc: https://github.com/freeboson/jenni-misc/ 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | 12 | NOTE: if your bot is not run on an IP that has access to OED, you should disable 13 | this module. (todo: add support for ezproxy library login) 14 | ''' 15 | 16 | from lxml import etree 17 | from StringIO import StringIO 18 | import web 19 | from HTMLParser import HTMLParser 20 | import re, sys 21 | 22 | 23 | oed = r'http://www.oed.com/srupage' 24 | oed_url = r'http://www.oed.com/srupage?operation=searchRetrieve&query=cql.serverChoice+=+{}&maximumRecords=100&startRecord=1' 25 | srw = r'{http://www.loc.gov/zing/srw/}' 26 | sru_dc = r'{info:srw/schema/1/dc-v1.1}' 27 | dc = r'{http://purl.org/dc/elements/1.1/}' 28 | 29 | 30 | 31 | #quick and dirty 32 | rm_disp = re.compile(r'<[/]*display>') 33 | rm_span = re.compile(r'<[/]*span[^>]*>') 34 | sub_em = re.compile(r'<[/]*em>') 35 | sub_strong = re.compile(r'<[/]*strong>') 36 | 37 | hparse = HTMLParser() 38 | 39 | 40 | def search(query): 41 | defs = list() 42 | 43 | result_xml = web.get(oed_url.format(web.urllib.quote(query))) 44 | 45 | result_tree = etree.parse(StringIO(result_xml)) 46 | root = result_tree.getroot() 47 | 48 | num_records = root.find(srw + 'numberOfRecords') 49 | if num_records is None: 50 | return 51 | else: 52 | num_records = int(num_records.text) 53 | 54 | if num_records < 1: 55 | return 56 | 57 | records = root.find(srw + 'records').getiterator() 58 | 59 | for record in records: 60 | rdata = record.find(srw + 'recordData') 61 | if rdata is not None: 62 | data = rdata.find(sru_dc + 'dc') 63 | title = hparse.unescape(data.find(dc + 'title').text).encode('utf-8') 64 | desc = hparse.unescape(clean_desc(data.find(dc + 'description').text.encode('utf-8'))) 65 | defs.append(title + ' :: ' + desc) 66 | 67 | return (num_records, defs) 68 | 69 | def clean_desc(desc): 70 | desc = rm_disp.sub('', desc) 71 | desc = rm_span.sub('', desc) 72 | desc = sub_em.sub('/', desc) 73 | desc = sub_strong.sub('\x02', desc) 74 | return desc 75 | 76 | def oed(jenni, input): 77 | '''.oed -- Look up definitions in the Oxford English Dictionary''' 78 | word = input.group(2) 79 | 80 | try: 81 | (num, defns) = search(word) 82 | except: 83 | jenni.say("[OED] Couldn't look up " + word + '.') 84 | return 85 | 86 | if num < 1 or len(defns) < 1: 87 | jenni.say("[OED] No results for " + word + '.') 88 | return 89 | 90 | long_def = "[OED] " + str(num) + " record(s). " + " | ".join(defns[:10]) 91 | if len(long_def) > 300: 92 | long_def = long_def[:295] + '[...]' 93 | 94 | jenni.say(long_def) 95 | 96 | oed.commands = ['oed'] 97 | oed.priority = 'high' 98 | 99 | 100 | -------------------------------------------------------------------------------- /modules/ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | ping.py - jenni Ping Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import random 13 | 14 | 15 | def interjection(jenni, input): 16 | """response to interjections""" 17 | jenni.say(input.nick + '!') 18 | interjection.rule = r'($nickname!)' 19 | interjection.priority = 'high' 20 | interjection.example = '$nickname!' 21 | 22 | 23 | def f_ping(jenni, input): 24 | """ping jenni in a channel or pm""" 25 | jenni.reply('pong!') 26 | f_ping.rule = r'(?i)$nickname[:,]?\sping' 27 | f_ping.priority = 'high' 28 | f_ping.example = '$nickname: ping!' 29 | 30 | if __name__ == '__main__': 31 | print __doc__.strip() 32 | -------------------------------------------------------------------------------- /modules/proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | proxy.py - Web Facilities 4 | 5 | More info: 6 | * Willie: https://willie.dftba.net 7 | * jenni: https://github.com/myano/jenni/ 8 | * Phenny: http://inamidst.com/phenny/ 9 | """ 10 | 11 | import json 12 | import re 13 | import urllib 14 | import time 15 | 16 | user_agent = 'Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0' 17 | 18 | 19 | class Grab(urllib.URLopener): 20 | def __init__(self, *args): 21 | self.version = user_agent 22 | urllib.URLopener.__init__(self, *args) 23 | 24 | def http_error_default(self, url, fp, errcode, errmsg, headers): 25 | return urllib.addinfourl(fp, [headers, errcode], "http:" + url) 26 | urllib._urlopener = Grab() 27 | 28 | 29 | def remote_call(uri, size=0, info=False): 30 | pyurl = u'https://tumbolia-two.appspot.com/py/' 31 | code = 'import json;' 32 | #code += "req=urllib2.Request(%s,headers={'Accept':'*/*'});" 33 | #code += "req.add_header('User-Agent','%s');" % (user_agent) 34 | #code += "u=urllib2.urlopen(req);" 35 | 36 | code += 'opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(),' 37 | code += 'urllib2.BaseHandler(), urllib2.HTTPHandler(),' 38 | code += 'urllib2.HTTPRedirectHandler(), urllib2.HTTPErrorProcessor(),' 39 | code += 'urllib2.UnknownHandler());' 40 | code += 'urllib2.install_opener(opener);' 41 | code += "req=urllib2.Request(%s, headers={'Accept':'*/*'});" 42 | code += "req.add_header('User-Agent', '%s');" % (user_agent) 43 | code += "u=urllib2.urlopen(req);" 44 | 45 | code += "rtn=dict();" 46 | if info: 47 | code += "rtn['info']=u.info();" 48 | else: 49 | code += "rtn['headers']=u.headers.dict;" 50 | if size: 51 | code += "contents=u.read(" 52 | code += str(size) 53 | code += ");" 54 | else: 55 | code += "contents=u.read();" 56 | 57 | code += "con=str();" 58 | code += r'''exec "try: con=(contents).decode('utf-8')\n''' 59 | code += '''except: con=(contents).decode('iso-8859-1')";''' 60 | code += "rtn['read']=con;" 61 | code += "rtn['url']=u.url;" 62 | code += "rtn['geturl']=u.geturl();" 63 | code += "rtn['code']=u.code;" 64 | code += "print json.dumps(rtn)" 65 | query = code % repr(uri) 66 | temp = urllib.quote(query) 67 | u = urllib.urlopen(pyurl + temp) 68 | results = u.read() 69 | u.close() 70 | 71 | try: 72 | useful = json.loads(results) 73 | return True, useful 74 | except Exception, error: 75 | return False, str(results) 76 | 77 | 78 | def get(uri): 79 | if not uri.startswith('http'): 80 | return 81 | status, u = remote_call(uri) 82 | if status: 83 | page = u['read'] 84 | else: 85 | page = str() 86 | return page 87 | 88 | 89 | def get_more(uri, size=0): 90 | if not uri.startswith('http'): 91 | uri = 'http://' + uri 92 | 93 | if size: 94 | status, response = remote_call(uri, size=size) 95 | else: 96 | status, response = remote_call(uri) 97 | return status, response 98 | 99 | 100 | def head(uri): 101 | if not uri.startswith('http'): 102 | uri = 'http://' + uri 103 | status, response = remote_call(uri, info=True) 104 | if status: 105 | page = response['info'] 106 | else: 107 | page = str() 108 | return page 109 | 110 | 111 | if __name__ == "__main__": 112 | print __doctype__.strip() 113 | -------------------------------------------------------------------------------- /modules/pun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pun.py - jenni Pun Module 4 | Copyright 2009-2015, yano (yanovich.net) 5 | Copyright 2008-2015, Sean B. Palmer (inamidst.com) 6 | Copyright 2015, Jonathan Arnett (j3rn.com) 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re 14 | import web 15 | 16 | 17 | def puns(jenni, input): 18 | url = 'http://www.punoftheday.com/cgi-bin/randompun.pl' 19 | exp = re.compile(r'
    \n

    (.*?)

    \n
    ') 20 | page = web.get(url) 21 | 22 | result = exp.search(page) 23 | if result: 24 | pun = result.groups()[0] 25 | return jenni.say(pun) 26 | else: 27 | return jenni.say("I'm afraid I'm not feeling punny today!") 28 | puns.commands = ['puns', 'pun', 'badpun', 'badpuns'] 29 | 30 | if __name__ == '__main__': 31 | print __doc__.strip() 32 | -------------------------------------------------------------------------------- /modules/quote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | quote.py - jenni Quote Module 4 | Copyright 2008-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import random 13 | import itertools 14 | from modules import unicode as uc 15 | 16 | def write_addquote(text): 17 | fn = open('quotes.txt', 'a') 18 | output = uc.encode(text) 19 | fn.write(output) 20 | fn.write('\n') 21 | fn.close() 22 | 23 | 24 | def addquote(jenni, input): 25 | '''.addquote something they said here -- adds the quote to the quote database.''' 26 | text = input.group(2) 27 | if not text: 28 | return jenni.say('No quote provided') 29 | 30 | write_addquote(text) 31 | 32 | jenni.reply('Quote added.') 33 | addquote.commands = ['addquote'] 34 | addquote.priority = 'low' 35 | addquote.example = '.addquote' 36 | 37 | 38 | def retrievequote(jenni, input): 39 | '''.quote -- displays a given quote''' 40 | NO_QUOTES = 'There are currently no quotes saved.' 41 | text = input.group(2) 42 | if text: 43 | text = text.strip() 44 | text = text.split()[0] 45 | 46 | try: 47 | fn = open('quotes.txt', 'r') 48 | except: 49 | return jenni.reply('Please add a quote first.') 50 | 51 | lines = fn.readlines() 52 | if len(lines) < 1: 53 | return jenni.reply(NO_QUOTES) 54 | MAX = len(lines) 55 | fn.close() 56 | random.seed() 57 | 58 | if text != None: 59 | try: 60 | number = int(text) 61 | if number < 0: 62 | number = MAX - abs(number) + 1 63 | except: 64 | nick = "<" + text + ">" 65 | 66 | indices = range(1, len(lines) + 1) 67 | selectors = map(lambda x: x.split()[0] == nick, lines) 68 | filtered_indices = list(itertools.compress(indices, selectors)) 69 | 70 | if len(filtered_indices) < 1: 71 | return jenni.say('No quotes by that nick!') 72 | 73 | filtered_index_index = random.randint(1, len(filtered_indices)) 74 | number = filtered_indices[filtered_index_index - 1] 75 | else: 76 | number = random.randint(1, MAX) 77 | if not (0 <= number <= MAX): 78 | jenni.reply("I'm not sure which quote you would like to see.") 79 | else: 80 | if lines: 81 | if number == 0: 82 | return jenni.say('There is no "0th" quote!') 83 | else: 84 | line = lines[number - 1] 85 | jenni.say('Quote %s of %s: ' % (number, MAX) + line) 86 | else: 87 | jenni.reply(NO_QUOTES) 88 | retrievequote.commands = ['quote'] 89 | retrievequote.priority = 'low' 90 | retrievequote.example = '.quote' 91 | 92 | 93 | def delquote(jenni, input): 94 | '''.rmquote -- removes a given quote from the database. Can only be done by the owner of the bot.''' 95 | if not input.owner: 96 | return 97 | text = input.group(2) 98 | number = int() 99 | 100 | try: 101 | fn = open('quotes.txt', 'r') 102 | except: 103 | return jenni.reply('No quotes to delete.') 104 | 105 | lines = fn.readlines() 106 | MAX = len(lines) 107 | fn.close() 108 | 109 | try: 110 | number = int(text) 111 | except: 112 | jenni.reply('Please enter the quote number you would like to delete.') 113 | return 114 | 115 | if number > 0: 116 | newlines = lines[:number - 1] + lines[number:] 117 | elif number == 0: 118 | return jenni.reply('There is no "0th" quote!') 119 | elif number == -1: 120 | newlines = lines[:number] 121 | else: 122 | ## number < -1 123 | newlines = lines[:number] + lines[number + 1:] 124 | fn = open('quotes.txt', 'w') 125 | for line in newlines: 126 | txt = line 127 | if txt: 128 | fn.write(txt) 129 | if txt[-1] != '\n': 130 | fn.write('\n') 131 | fn.close() 132 | jenni.reply('Successfully deleted quote %s.' % (number)) 133 | delquote.commands = ['rmquote', 'delquote'] 134 | delquote.priority = 'low' 135 | delquote.example = '.rmquote' 136 | 137 | 138 | def grabquote(jenni, input): 139 | try: 140 | from modules import find 141 | except: 142 | return jenni.say('Could not load "find" module.') 143 | 144 | txt = input.group(2) 145 | 146 | if not txt: 147 | return jenni.say('Please provide a nick for me to look for recent activity.') 148 | 149 | parts = txt.split() 150 | 151 | if not parts: 152 | return jenni.say('Please provide me with a valid nick.') 153 | 154 | nick = parts[0] 155 | channel = input.sender 156 | channel = (channel).lower() 157 | 158 | quote_db = find.load_db() 159 | if quote_db and channel in quote_db and nick in quote_db[channel]: 160 | quotes_by_nick = quote_db[channel][nick] 161 | else: 162 | return jenni.say('There are currently no existing quotes by the provided nick in this channel.') 163 | 164 | quote_by_nick = quotes_by_nick[-1] 165 | 166 | quote = '<%s> %s' % (nick, quote_by_nick) 167 | 168 | write_addquote(quote) 169 | 170 | jenni.say('quote added: %s' % (quote)) 171 | grabquote.commands = ['grab'] 172 | 173 | 174 | if __name__ == '__main__': 175 | print __doc__.strip() 176 | -------------------------------------------------------------------------------- /modules/rand.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | rand.py - jenni Rand Module 4 | Copyright 2010-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import random 13 | import re 14 | 15 | def rand(jenni, input): 16 | """.rand - Generates a random integer between and .""" 17 | if input.group(2) == " " or not input.group(2): 18 | jenni.reply("I'm sorry, but you must enter at least one number.") 19 | else: 20 | random.seed() 21 | li_integers = input.group(2) 22 | li_integers_str = li_integers.split() 23 | if len(li_integers_str) == 1: 24 | li_integers_str = re.sub(r'\D', '', str(li_integers_str)) 25 | if len(li_integers_str) > 0: 26 | if int(li_integers_str[0]) <= 1: 27 | a = li_integers_str 28 | a = int(a) 29 | if a < 0: 30 | randinte = random.randint(a, 0) 31 | if a > 0: 32 | randinte = random.randint(0, a) 33 | else: 34 | a = li_integers_str 35 | a = int(a) 36 | randinte = random.randint(0, a) 37 | jenni.reply("your random integer is: " + str(randinte)) 38 | else: 39 | jenni.reply("lolwut") 40 | else: 41 | ln = li_integers.split() 42 | if len(ln) == 2: 43 | a, b = ln 44 | a = re.sub(r'\D', u'', a) 45 | b = re.sub(r'\D', u'', b) 46 | if not a: 47 | a = 0 48 | if not b: 49 | b = 0 50 | a = int(a) 51 | b = int(b) 52 | if a <= b: 53 | randinte = random.randint(a, b) 54 | else: 55 | randinte = random.randint(b, a) 56 | jenni.reply("your random integer is: " + str(randinte)) 57 | else: 58 | jenni.reply("I'm not sure what you want me to do!") 59 | 60 | rand.commands = ['rand'] 61 | rand.priority = 'medium' 62 | 63 | if __name__ == '__main__': 64 | print __doc__.strip() 65 | -------------------------------------------------------------------------------- /modules/reload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | reload.py - jenni Module Reloader Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import sys, os.path, time, imp 14 | import irc 15 | 16 | def f_reload(jenni, input): 17 | """Reloads a module, for use by admins only.""" 18 | if not input.admin: return 19 | 20 | name = input.group(2) 21 | if name == jenni.config.owner: 22 | return jenni.reply('What?') 23 | 24 | if (not name) or (name == '*'): 25 | jenni.variables = None 26 | jenni.commands = None 27 | jenni.setup() 28 | return jenni.reply('done') 29 | 30 | if not sys.modules.has_key(name): 31 | return jenni.reply('%s: no such module!' % name) 32 | 33 | # Thanks to moot for prodding me on this 34 | path = sys.modules[name].__file__ 35 | if path.endswith('.pyc') or path.endswith('.pyo'): 36 | path = path[:-1] 37 | if not os.path.isfile(path): 38 | return jenni.reply('Found %s, but not the source file' % name) 39 | 40 | module = imp.load_source(name, path) 41 | sys.modules[name] = module 42 | if hasattr(module, 'setup'): 43 | module.setup(jenni) 44 | 45 | mtime = os.path.getmtime(module.__file__) 46 | modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime)) 47 | 48 | jenni.register(vars(module)) 49 | jenni.bind_commands() 50 | 51 | jenni.reply('%r (version: %s)' % (module, modified)) 52 | if hasattr(jenni.config, 'logchan_pm'): 53 | if not input.owner: 54 | jenni.msg(jenni.config.logchan_pm, 'RELOADED: %r -- (%s, %s) - %s' % (module, input.sender, input.nick, modified)) 55 | f_reload.name = 'reload' 56 | f_reload.rule = ('$nick', ['reload'], r'(\S+)?') 57 | f_reload.priority = 'low' 58 | f_reload.thread = False 59 | 60 | if __name__ == '__main__': 61 | print __doc__.strip() 62 | -------------------------------------------------------------------------------- /modules/roulette.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | roulette.py - jenni Roulette Game Module 4 | Copyright 2010-2013, Kenneth Sham 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import random 13 | from datetime import datetime, timedelta 14 | random.seed() 15 | 16 | # edit this setting for roulette counter. Larger, the number, the harder the game. 17 | ROULETTE_SETTINGS = { 18 | # the bigger the MAX_RANGE, the harder/longer the game will be 19 | 'MAX_RANGE' : 5, 20 | 21 | # game timeout in minutes (default is 1 minute) 22 | 'INACTIVE_TIMEOUT' : 1, 23 | } 24 | 25 | # edit this setting for text displays 26 | ROULETTE_STRINGS = { 27 | 'TICK' : '*TICK*', 28 | 'KICK_REASON' : '*SNIPED! YOU LOSE!*', 29 | 'GAME_END' : 'Game stopped.', 30 | 'GAME_END_FAIL' : "%s: Please wait %s seconds to stop Roulette.", 31 | } 32 | 33 | ## do not edit below this line unless you know what you're doing 34 | ROULETTE_TMP = { 35 | 'LAST-PLAYER' : None, 36 | 'NUMBER' : None, 37 | 'TIMEOUT' :timedelta(minutes=ROULETTE_SETTINGS['INACTIVE_TIMEOUT']), 38 | 'LAST-ACTIVITY' : None, 39 | } 40 | 41 | def roulette (jenni, input): 42 | global ROULETTE_SETTINGS, ROULETTE_STRINGS, ROULETTE_TMP 43 | if ROULETTE_TMP['NUMBER'] is None: 44 | ROULETTE_TMP['NUMBER'] = random.randint(0,ROULETTE_SETTINGS['MAX_RANGE']) 45 | ROULETTE_TMP['LAST-PLAYER'] = input.nick 46 | ROULETTE_TMP['LAST-ACTIVITY'] = datetime.now() 47 | jenni.reply(ROULETTE_STRINGS['TICK']) 48 | return 49 | if ROULETTE_TMP['LAST-PLAYER'] == input.nick: 50 | return 51 | ROULETTE_TMP['LAST-ACTIVITY'] = datetime.now() 52 | ROULETTE_TMP['LAST-PLAYER'] = input.nick 53 | if ROULETTE_TMP['NUMBER'] == random.randint(0,ROULETTE_SETTINGS['MAX_RANGE']): 54 | jenni.write(['KICK', '%s %s :%s' % (input.sender, input.nick, ROULETTE_STRINGS['KICK_REASON'])]) 55 | ROULETTE_TMP['LAST-PLAYER'] = None 56 | ROULETTE_TMP['NUMBER'] = None 57 | ROULETTE_TMP['LAST-ACTIVITY'] = None 58 | else: 59 | jenni.reply(ROULETTE_STRINGS['TICK']) 60 | roulette.commands = ['roulette'] 61 | roulette.priority = 'low' 62 | roulette.rate = 60 63 | 64 | def rouletteStop (jenni, input): 65 | global ROULETTE_TMP, ROULETTE_STRINGS 66 | if ROULETTE_TMP['LAST-PLAYER'] is None: 67 | return 68 | if datetime.now() - ROULETTE_TMP['LAST-ACTIVITY'] > ROULETTE_TMP['TIMEOUT']: 69 | jenni.reply(ROULETTE_STRINGS['GAME_END']) 70 | ROULETTE_TMP['LAST-ACTIVITY'] = None 71 | ROULETTE_TMP['LAST-PLAYER'] = None 72 | ROULETTE_TMP['NUMBER'] = None 73 | else: 74 | jenni.reply(ROULETTE_STRINGS['GAME_END_FAIL'] % (input.nick, ROULETTE_TMP['TIMEOUT'].seconds - (datetime.now() - ROULETTE_TMP['LAST-ACTIVITY']).seconds)) 75 | rouletteStop.commands = ['roulette-stop'] 76 | roulette.priority = 'low' 77 | roulette.rate = 60 78 | 79 | if __name__ == '__main__': 80 | print __doc__.strip() 81 | -------------------------------------------------------------------------------- /modules/sasl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | sasl.py - jenni SASL Authentication module 4 | """ 5 | import base64 6 | 7 | def irc_cap (jenni, input): 8 | cap, value = input.args[1], input.args[2] 9 | rq = '' 10 | 11 | if jenni.is_connected: 12 | return 13 | 14 | if cap == 'LS': 15 | if 'multi-prefix' in value: 16 | rq += ' multi-prefix' 17 | if 'sasl' in value: 18 | rq += ' sasl' 19 | 20 | if not rq: 21 | irc_cap_end(jenni, input) 22 | else: 23 | if rq[0] == ' ': 24 | rq = rq[1:] 25 | 26 | jenni.write(('CAP', 'REQ', ':' + rq)) 27 | 28 | elif cap == 'ACK': 29 | if 'sasl' in value: 30 | jenni.write(('AUTHENTICATE', 'PLAIN')) 31 | else: 32 | irc_cap_end(jenni, input) 33 | 34 | elif cap == 'NAK': 35 | irc_cap_end(jenni, input) 36 | 37 | else: 38 | irc_cap_end(jenni, input) 39 | 40 | return 41 | irc_cap.rule = r'(.*)' 42 | irc_cap.event = 'CAP' 43 | irc_cap.priority = 'high' 44 | 45 | 46 | def irc_authenticated (jenni, input): 47 | auth = False 48 | if hasattr(jenni.config, 'nick') and jenni.config.nick is not None and hasattr(jenni.config, 'password') and jenni.config.password is not None: 49 | nick = jenni.config.nick 50 | password = jenni.config.password 51 | 52 | # If provided, use the specified user for authentication, otherwise just use the nick 53 | if hasattr(jenni.config, 'user') and jenni.config.user is not None: 54 | user = jenni.config.user 55 | else: 56 | user = nick 57 | 58 | auth = "\0".join((nick, user, password)) 59 | auth = base64.b64encode(auth) 60 | 61 | if not auth: 62 | jenni.write(('AUTHENTICATE', '+')) 63 | else: 64 | while len(auth) >= 400: 65 | out = auth[0:400] 66 | auth = auth[401:] 67 | jenni.write(('AUTHENTICATE', out)) 68 | 69 | if auth: 70 | jenni.write(('AUTHENTICATE', auth)) 71 | else: 72 | jenni.write(('AUTHENTICATE', '+')) 73 | 74 | return 75 | irc_authenticated.rule = r'(.*)' 76 | irc_authenticated.event = 'AUTHENTICATE' 77 | irc_authenticated.priority = 'high' 78 | 79 | 80 | def irc_903 (jenni, input): 81 | jenni.is_authenticated = True 82 | irc_cap_end(jenni, input) 83 | return 84 | irc_903.rule = r'(.*)' 85 | irc_903.event = '903' 86 | irc_903.priority = 'high' 87 | 88 | 89 | def irc_904 (jenni, input): 90 | irc_cap_end(jenni, input) 91 | return 92 | irc_904.rule = r'(.*)' 93 | irc_904.event = '904' 94 | irc_904.priority = 'high' 95 | 96 | 97 | def irc_905 (jenni, input): 98 | irc_cap_end(jenni, input) 99 | return 100 | irc_905.rule = r'(.*)' 101 | irc_905.event = '905' 102 | irc_905.priority = 'high' 103 | 104 | 105 | def irc_906 (jenni, input): 106 | irc_cap_end(jenni, input) 107 | return 108 | irc_906.rule = r'(.*)' 109 | irc_906.event = '906' 110 | irc_906.priority = 'high' 111 | 112 | 113 | def irc_907 (jenni, input): 114 | irc_cap_end(jenni, input) 115 | return 116 | irc_907.rule = r'(.*)' 117 | irc_907.event = '907' 118 | irc_907.priority = 'high' 119 | 120 | 121 | def irc_001 (jenni, input): 122 | jenni.is_connected = True 123 | return 124 | irc_001.rule = r'(.*)' 125 | irc_001.event = '001' 126 | irc_001.priority = 'high' 127 | 128 | 129 | def irc_cap_end (jenni, input): 130 | jenni.write(('CAP', 'END')) 131 | return 132 | 133 | 134 | if __name__ == '__main__': 135 | print __doc__.strip() 136 | -------------------------------------------------------------------------------- /modules/seen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | seen.py - jenni Seen Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import time 14 | 15 | ## TODO: Make it save .db to disk 16 | 17 | def f_seen(jenni, input): 18 | """.seen - Reports when was last seen.""" 19 | 20 | if not input.group(2): 21 | return jenni.say('Please provide a nick.') 22 | nick = input.group(2).lower() 23 | 24 | if not hasattr(jenni, 'seen'): 25 | return jenni.reply('?') 26 | 27 | if jenni.seen.has_key(nick): 28 | channel, t = jenni.seen[nick] 29 | t = time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime(t)) 30 | msg = 'I last saw %s at %s in some channel.' % (nick, t) 31 | jenni.say(msg) 32 | else: 33 | jenni.say("Sorry, I haven't seen %s around." % nick) 34 | f_seen.rule = r'(?i)^\.(seen)\s+(\w+)' 35 | f_seen.rate = 15 36 | 37 | def f_note(jenni, input): 38 | try: 39 | if not hasattr(jenni, 'seen'): 40 | jenni.seen = dict() 41 | if input.sender.startswith('#'): 42 | jenni.seen[input.nick.lower()] = (input.sender, time.time()) 43 | except Exception, e: print e 44 | f_note.rule = r'(.*)' 45 | f_note.priority = 'low' 46 | 47 | if __name__ == '__main__': 48 | print __doc__.strip() 49 | -------------------------------------------------------------------------------- /modules/slap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | scores.py - jenni Slap Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | 6 | More info: 7 | * jenni: https://github.com/myano/jenni/ 8 | * Phenny: http://inamidst.com/phenny/ 9 | """ 10 | 11 | import random 12 | 13 | def slap(jenni, input): 14 | """.slap - Slaps """ 15 | text = input.group().split() 16 | if len(text) < 2 or text[1].startswith('#'): return 17 | if text[1] == jenni.nick: 18 | if (input.nick not in jenni.config.admins): 19 | text[1] = input.nick 20 | else: text[1] = 'herself' 21 | if text[1] in jenni.config.admins: 22 | if (input.nick not in jenni.config.admins): 23 | text[1] = input.nick 24 | verb = random.choice(('slaps', 'kicks', 'destroys', 'annihilates', 'obliterates', 'drop kicks', 'curb stomps', 'backhands', 'punches', 'roundhouse kicks', 'rusty hooks', 'pwns', 'owns')) 25 | jenni.write(['PRIVMSG', input.sender, ' :\x01ACTION', verb, text[1], '\x01']) 26 | slap.commands = ['slap', 'slaps'] 27 | slap.priority = 'medium' 28 | slap.rate = 60 29 | 30 | if __name__ == '__main__': 31 | print __doc__.strip() 32 | -------------------------------------------------------------------------------- /modules/southpark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | southpark.py - jenni Southpark Module 4 | Copyright 2011-2013, yano (yanovich.net) and Kenneth Sham (Kays) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | from datetime import datetime, timedelta 13 | import web, time, re 14 | 15 | STRINGS = 'The next new episode of South Park will air on \x0300%s\x03, which is in: %s.' 16 | 17 | HTMLEntities = { 18 | ' ' : ' ', 19 | '<' : '<', 20 | '>' : '>', 21 | '&' : '&', 22 | '"' : '"', 23 | ''' : "'" 24 | } 25 | 26 | cache = { 27 | 'TIMES' : None, 28 | 'NEW-EPI' : None 29 | } 30 | cachets = { 31 | 'TIMES' : None, 32 | 'NEW-EPI' : None 33 | } 34 | 35 | months = { 36 | 'January' : 1, 37 | 'February' : 2, 38 | 'March' : 3, 39 | 'April' : 4, 40 | 'May' : 5, 41 | 'June' : 6, 42 | 'July' : 7, 43 | 'August' : 8, 44 | 'September' : 9, 45 | 'October' : 10, 46 | 'November' : 11, 47 | 'December' : 12 48 | } 49 | 50 | cachetsreset = timedelta(hours=6) 51 | maxtitlelen = 0 52 | maxepilen = 0 53 | 54 | def htmlDecode (html): 55 | for k, v in HTMLEntities.iteritems(): html = html.replace(k, v) 56 | return html 57 | 58 | def southpark (jenni, input): 59 | global cache, cachets 60 | text = input.group().split() 61 | if len(text) > 1: 62 | if text[1] == 'cleartimes': 63 | cache['TIMES'] = None 64 | cachets['TIMES'] = None 65 | jenni.reply("Southpark times successfully cleared.") 66 | return 67 | elif text[1] == 'clearnewep': 68 | cache['NEW-EPI'] = None 69 | cachets['NEW-EPI'] = None 70 | jenni.reply("New episodes cleared from cache.") 71 | elif text[1] == 'times': 72 | southparktimes(jenni,input) 73 | return 74 | else: 75 | getNewShowDate(jenni) 76 | southpark.commands = ['southpark'] 77 | southpark.priority = 'low' 78 | southpark.rate = 30 79 | 80 | def getNewShowDate (jenni): 81 | global cache, cachets 82 | tsnow = datetime.now() 83 | if cache['NEW-EPI'] is not None and cachets['NEW-EPI'] is not None and tsnow - cachets['NEW-EPI'] <= cachetsreset: 84 | gc = getcountdown(cache['NEW-EPI']) 85 | msg = STRINGS % (cache['NEW-EPI'], gc) 86 | jenni.say(msg) 87 | return 88 | 89 | today = time.localtime() 90 | src = web.get('https://en.wikipedia.org/wiki/List_of_South_Park_episodes') 91 | parts = src.split('Season 15 (2011)') 92 | cont = parts.pop() 93 | parts = cont.split('Shorts and unaired episodes') 94 | cont = parts[0] 95 | tds = cont.split('') 96 | data = None 97 | for i in range(len(tds)): 98 | m = re.match('^[A-Z][a-z]{2,8} \d{1,2}, \d{4}', tds[i]) 99 | if m is None: 100 | continue 101 | else: 102 | dt = time.strptime(m.group(), "%B %d, %Y") 103 | if dt < today: 104 | continue 105 | else: 106 | cache['NEW-EPI'] = m.group() 107 | cachets['NEW-EPI'] = tsnow 108 | rcd = getcountdown(m.group()) 109 | msg = STRINGS % (m.group(), rcd) 110 | jenni.say(msg) 111 | break 112 | 113 | def getcountdown (x): 114 | mon = x.split()[0] 115 | day = (x.split()[1][:-1]) 116 | yr = int(x.split()[2]) 117 | month = months[mon] 118 | diff = datetime(int(yr), int(month), int(day), 22, 00, 00) - datetime.today() 119 | weeks, days = divmod(diff.days, 7) 120 | minutes, seconds = divmod(diff.seconds, 60) 121 | hours, minutes = divmod(minutes, 60) 122 | return "%s days, %s hours, %s minutes, and %s seconds" % (days, hours, minutes, seconds) 123 | 124 | def southparktimes (jenni, input): 125 | global cache, cachets, maxtitlelen, maxepilen 126 | tsnow = datetime.now() 127 | if cache['TIMES'] is not None and cachets['TIMES'] is not None and tsnow - cachets['TIMES'] <= cachetsreset: 128 | printListings(jenni) 129 | return 130 | 131 | src = web.get('http://www.comedycentral.com/tv_schedule/index.jhtml?seriesId=11600&forever=please') 132 | parts = src.split('
    ') 133 | cont = parts[1] 134 | parts = cont.split('
    ') 135 | cont = parts[0] 136 | schedule = cont.split('
    ') 137 | del schedule[0] 138 | 139 | info = [] 140 | count = 5 141 | for s in schedule: 142 | s = s.replace('\n',' ') 143 | s = htmlDecode(s) 144 | 145 | ## gets the date 146 | sidx = s.index('
    ') 147 | send = s.index('
    ', sidx) 148 | if sidx == -1 or send == -1: break 149 | 150 | m = re.search('>([^<]{2,})$', s[sidx:send]) 151 | if m is None: break 152 | 153 | date = m.group(1).strip() 154 | sdate = time.strptime(date, '%A %b %d %Y') 155 | 156 | ## get episodes for the s-th date 157 | tepi = s.split('') 163 | send = t.index('', sidx) 164 | 165 | if sidx == -1 or send == -1: break 166 | 167 | stime = t[sidx+6:send].strip() 168 | 169 | ## gets the schedule episode name 170 | sidx = t.index('', send) 171 | send = t.index('', sidx) 172 | 173 | if sidx == -1 or send == -1: break 174 | 175 | stitle = t[sidx+3:send].strip() 176 | m = re.search('\(([^)]+)\)$', stitle) 177 | if m is None: break 178 | sepi = str(int(m.group(1))) 179 | if len(sepi) > maxepilen: maxepilen = len(sepi) 180 | stitle = stitle.replace(m.group(), '') 181 | lenstitle = len(stitle) 182 | if lenstitle > maxtitlelen: maxtitlelen = lenstitle 183 | 184 | ## gets the schedule episode desc 185 | sidx = send 186 | send = t.index('', sidx) 187 | 188 | if send == -1: break 189 | 190 | m = re.search('>([^<]{2,})$', t[sidx:send]) 191 | 192 | if m is None: break 193 | 194 | sdesc = m.group(1).strip() 195 | 196 | info.append([sdate, sepi, stitle, stime]) 197 | 198 | count -= 1 199 | if count == 0: break 200 | if count == 0: break 201 | cache['TIMES'] = info 202 | cachets['TIMES'] = tsnow 203 | printListings(jenni) 204 | 205 | def printListings (jenni): 206 | for i in cache['TIMES']: 207 | jenni.say('%s: #%s - \x02%s\x02 %s (%s) %s' % (time.strftime('%a %b %d', i[0]), i[1]+' '*(maxepilen-len(i[1])), i[2], ' '*(maxtitlelen-len(i[2])+5) , i[3], 'Comedy Central')) 208 | 209 | if __name__ == '__main__': 210 | print __doc__.strip() 211 | -------------------------------------------------------------------------------- /modules/spotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | spotify.py - An api interface for spotify lookups 5 | Copyright 2015 - 2016 Micheal Harker 6 | Copyright 2012 Patrick Andrew 7 | 8 | Licensed under the Eiffel Forum License, version 2 9 | 10 | 1. Permission is hereby granted to use, copy, modify and/or 11 | distribute this package, provided that: 12 | * copyright notices are retained unchanged, 13 | * any distribution of this package, whether modified or not, 14 | includes this license text. 15 | 2. Permission is hereby also granted to distribute binary programs 16 | which depend on this package. If the binary program depends on a 17 | modified version of this package, you are encouraged to publicly 18 | release the modified version of this package. 19 | 20 | THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT WARRANTY. ANY EXPRESS OR 21 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS PACKAGE. 26 | """ 27 | 28 | import json 29 | import sys 30 | from datetime import timedelta 31 | 32 | from modules import proxy 33 | 34 | 35 | class NotModifiedError(Exception): 36 | def __init__(self): 37 | super(NotModifiedError, self).__init__( 38 | "The data hasn't changed since your last request.") 39 | 40 | 41 | class ForbiddenError(Exception): 42 | def __init__(self): 43 | super(ForbiddenError, self).__init__( 44 | "The rate-limiting has kicked in. Please try again later.") 45 | 46 | 47 | class NotFoundException(LookupError): 48 | def __init__(self): 49 | super(NotFoundException, self).__init__( 50 | "Could not find that Spotify URI.") 51 | 52 | 53 | class BadRequestException(LookupError): 54 | def __init__(self): 55 | super(BadRequestException, self).__init__( 56 | "The request was not understood.") 57 | 58 | 59 | class InternalServerError(Exception): 60 | def __init__(self): 61 | super(InternalServerError, self).__init__( 62 | "The server encounted an unexpected problem.") 63 | 64 | 65 | class ServiceUnavailable(Exception): 66 | def __init__(self): 67 | super(ServiceUnavailable, self).__init__( 68 | "The API is temporarily unavailable.") 69 | 70 | SpotifyStatusCodes = { 71 | 304: NotModifiedError, 72 | 400: BadRequestException, 73 | 403: ForbiddenError, 74 | 404: NotFoundException, 75 | 500: InternalServerError, 76 | 503: ServiceUnavailable 77 | } 78 | 79 | TRACK_MSG = '"{0}{1}{0}" [{0}{2}{0}] by {3} from "{0}{4}{0}".' 80 | EXPLICIT_TRACK_MSG = '[{0}E{0}]" {0}{1}{0}" [{0}{2}{0}] by {3} from "{0}{4}{0}".' 81 | ALBUM_MSG = '"{0}{1}{0}" by {2}, released in {0}{3}{0}.' 82 | ARTIST_MSG = 'Artist: {0}{1}{0}' 83 | 84 | API_URL = "api.spotify.com" 85 | API_ENDPOINT = "/v1" 86 | 87 | 88 | def lookup(typ, objid): 89 | url = "https://%s%s/%ss/%s" % (API_URL, API_ENDPOINT, typ, objid) 90 | 91 | success, response = proxy.get_more(url) 92 | 93 | if not success: 94 | raise Exception("Unable to connect to proxy: {0}".format(response)) 95 | 96 | if response['code'] == 200: 97 | result = json.loads(response['read']) 98 | return result 99 | 100 | try: 101 | raise SpotifyStatusCodes[response['code']] 102 | except KeyError, ValueError: 103 | raise Exception("HTTP Error {0}".format(response['code'])) 104 | 105 | 106 | def notify(jenni, recipient, text): 107 | jenni.write(('NOTICE', recipient), text) 108 | 109 | 110 | def print_album(jenni, album): 111 | artist_names = [artist['name'] for artist in album['artists']] 112 | artists = artist_list(artist_names) 113 | 114 | message = ALBUM_MSG.format( 115 | "\x02", 116 | album['name'].encode('utf-8'), 117 | artists.encode('utf-8'), 118 | album['release_date'][:4] 119 | ) 120 | 121 | jenni.say(message) 122 | 123 | 124 | def print_artist(jenni, artist): 125 | message = ARTIST_MSG.format( 126 | "\x02", 127 | artist['name'].encode('utf-8') 128 | ) 129 | 130 | jenni.say(message) 131 | 132 | 133 | def print_track(jenni, track): 134 | length = str(timedelta(seconds=(track['duration_ms']/1000)))[2:7] 135 | if length[0] == '0': 136 | length = length[1:] 137 | 138 | artist_names = [artist['name'] for artist in track['artists']] 139 | artists = artist_list(artist_names) 140 | 141 | if track['explicit']: 142 | message_format = EXPLICIT_TRACK_MSG 143 | else: 144 | message_format = TRACK_MSG 145 | 146 | message = message_format.format( 147 | "\x02", 148 | track['name'].encode('utf-8'), 149 | length, 150 | artists.encode('utf-8'), 151 | track['album']['name'].encode('utf-8') 152 | ) 153 | 154 | jenni.say(message) 155 | 156 | 157 | def query(jenni, input): 158 | typ = (input.group(1) or input.group(3)).lower() # type of object we wanna lookup 159 | objid = input.group(2) or input.group(4) # ID of the object like a track, artist, etc. 160 | 161 | formatters = { 162 | 'track': print_track, 163 | 'album': print_album, 164 | 'artist': print_artist 165 | } 166 | 167 | if typ not in formatters: 168 | notify(jenni, input.nick, "Unknown object type: {0}".format(typ)) 169 | return 170 | 171 | try: 172 | result = lookup(typ, objid) 173 | except Exception as e: 174 | notify(jenni, input.nick, str(e)) 175 | return 176 | 177 | try: 178 | formatters[typ](jenni, result) 179 | except Exception as e: 180 | notify(jenni, input.nick, str(e)) 181 | 182 | query.rule = r'(?i).*\bspotify:(\S+):(\S+)|^\.sp(?:otify)? +https?://open\.spotify\.com/(\S+)/(\S+)$' 183 | query.priority = 'low' 184 | 185 | 186 | def artist_list(data): 187 | if (len(data) > 1): 188 | artists = "" 189 | for artist in data[:-1]: 190 | artists += "{0}{1}{0}".format("\x02", artist) 191 | if artist is not data[-2]: 192 | artists += ", " 193 | artists += " and {0}{1}{0}".format("\x02", data[-1]) 194 | return artists 195 | else: 196 | return "{0}{1}{0}".format("\x02", data[0]) 197 | 198 | 199 | if __name__ == '__main__': 200 | print __doc__.strip() 201 | -------------------------------------------------------------------------------- /modules/startup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | startup.py - jenni Startup Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import threading, time 14 | 15 | def setup(jenni): 16 | # by clsn 17 | jenni.data = {} 18 | refresh_delay = 300.0 19 | 20 | if hasattr(jenni.config, 'refresh_delay'): 21 | try: refresh_delay = float(jenni.config.refresh_delay) 22 | except: pass 23 | 24 | def close(): 25 | print "Nobody PONGed our PING, restarting" 26 | jenni.handle_close() 27 | 28 | def pingloop(): 29 | timer = threading.Timer(refresh_delay, close, ()) 30 | jenni.data['startup.setup.timer'] = timer 31 | jenni.data['startup.setup.timer'].start() 32 | # print "PING!" 33 | jenni.write(('PING', jenni.config.host)) 34 | jenni.data['startup.setup.pingloop'] = pingloop 35 | 36 | def pong(jenni, input): 37 | try: 38 | # print "PONG!" 39 | jenni.data['startup.setup.timer'].cancel() 40 | time.sleep(refresh_delay + 60.0) 41 | pingloop() 42 | except: pass 43 | pong.event = 'PONG' 44 | pong.thread = True 45 | pong.rule = r'.*' 46 | jenni.variables['pong'] = pong 47 | 48 | # Need to wrap handle_connect to start the loop. 49 | inner_handle_connect = jenni.handle_connect 50 | 51 | def outer_handle_connect(): 52 | inner_handle_connect() 53 | if jenni.data.get('startup.setup.pingloop'): 54 | jenni.data['startup.setup.pingloop']() 55 | 56 | jenni.handle_connect = outer_handle_connect 57 | 58 | def startup(jenni, input): 59 | import time 60 | 61 | if hasattr(jenni.config, 'serverpass') and not jenni.auth_attempted: 62 | jenni.write(('PASS', jenni.config.serverpass)) 63 | 64 | if not jenni.is_authenticated and hasattr(jenni.config, 'password'): 65 | if hasattr(jenni.config, 'user') and jenni.config.user is not None: 66 | user = jenni.config.user 67 | else: 68 | user = jenni.config.nick 69 | 70 | jenni.msg('NickServ', 'IDENTIFY %s %s' % (user, jenni.config.password)) 71 | time.sleep(10) 72 | 73 | # Cf. http://swhack.com/logs/2005-12-05#T19-32-36 74 | for channel in jenni.channels: 75 | jenni.write(('JOIN', channel)) 76 | time.sleep(0.5) 77 | startup.rule = r'(.*)' 78 | startup.event = '251' 79 | startup.priority = 'low' 80 | 81 | # Method for populating op/hop/voice information in channels on join 82 | def privs_on_join(jenni, input): 83 | if not input.mode_target or not input.mode_target.startswith('#'): 84 | return 85 | 86 | channel = input.mode_target 87 | if input.names and len(input.names) > 0: 88 | split_names = input.names.split() 89 | for name in split_names: 90 | nick_mode, nick = name[0], name[1:] 91 | if nick_mode == '@': 92 | jenni.add_op(channel, nick) 93 | elif nick_mode == '%': 94 | jenni.add_halfop(channel, nick) 95 | elif nick_mode == '+': 96 | jenni.add_voice(channel, nick) 97 | privs_on_join.rule = r'(.*)' 98 | privs_on_join.event = '353' 99 | privs_on_join.priority = 'high' 100 | 101 | # Method for tracking changes to ops/hops/voices in channels 102 | def track_priv_change(jenni, input): 103 | if not input.sender or not input.sender.startswith('#'): 104 | return 105 | 106 | channel = input.sender 107 | 108 | if input.mode: 109 | add_mode = input.mode.startswith('+') 110 | del_mode = input.mode.startswith('-') 111 | 112 | # Check that this is a mode change and that it is a mode change on a user 113 | if (add_mode or del_mode) and input.mode_target and len(input.mode_target) > 0: 114 | mode_change = input.mode[1:] 115 | mode_target = input.mode_target 116 | 117 | if add_mode: 118 | if mode_change == 'o': 119 | jenni.add_op(channel, mode_target) 120 | elif mode_change == 'h': 121 | jenni.add_halfop(channel, mode_target) 122 | elif mode_change == 'v': 123 | jenni.add_voice(channel, mode_target) 124 | else: 125 | if mode_change == 'o': 126 | jenni.del_op(channel, mode_target) 127 | elif mode_change == 'h': 128 | jenni.del_halfop(channel, mode_target) 129 | elif mode_change == 'v': 130 | jenni.del_voice(channel, mode_target) 131 | track_priv_change.rule = r'(.*)' 132 | track_priv_change.event = 'MODE' 133 | track_priv_change.priority = 'high' 134 | 135 | if __name__ == '__main__': 136 | print __doc__.strip() 137 | -------------------------------------------------------------------------------- /modules/strawpoll.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | strawpoll.py - jenni Strawpoll Module 4 | Copyright 2015, Bekey 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | ''' 11 | 12 | import urllib2 13 | import json 14 | import shlex 15 | import getopt 16 | 17 | def create_strawpoll_json(title, arguments, multi, permissive): 18 | data = {'options':arguments,'title':title,'multi':multi,'permissive':permissive} 19 | return json.dumps(data) 20 | 21 | def create_strawpoll_link(json_data): 22 | req = urllib2.Request('http://strawpoll.me/api/v2/polls') 23 | req.add_header('Content-Type', 'application/json') 24 | req.add_header('User-Agent', 'Mozilla/5.0 (Jenni)') 25 | 26 | response = urllib2.urlopen(req, json_data).read() 27 | strawpoll_id = json.loads(response)['id'] 28 | return "http://strawpoll.me/" + str(strawpoll_id) 29 | 30 | def strawpoll(jenni, input): 31 | """ .strawpoll --title='Example Title' 32 | [--multi] 33 | [--permissive] 34 | 'Option 1' 35 | 'Option 2' 36 | 'Option 3' 37 | 38 | Creates a strawpoll with the given title and options 39 | """ 40 | 41 | if not input.admin: 42 | return; 43 | 44 | arguments = shlex.split(input) 45 | try: 46 | optlist, arguments = getopt.getopt(arguments[1:], 't:mp', ['title=', 'multi', 'permissive']) 47 | except getopt.GetoptError as err: 48 | return jenni.say(str(err)) 49 | 50 | multi = False 51 | permissive = False 52 | title = "N/A" 53 | 54 | for o, a in optlist: 55 | if o in ("-m", "--multi"): 56 | multi = True 57 | elif o in ("-p", "--permissive"): 58 | permissive = True 59 | elif o in ("-t", "--title"): 60 | title = a 61 | else: 62 | return jenni.say("Unrecognized arguments.") 63 | 64 | if len(arguments) == 0: 65 | return jenni.say("No options found.") 66 | 67 | if len(arguments) > 30: 68 | return jenni.say("Cannot create strawpoll with more than 30 options.") 69 | 70 | try: 71 | data = create_strawpoll_json(title, arguments, multi, permissive) 72 | link = create_strawpoll_link(data) 73 | except Exception, e: 74 | return jenni.say(str(e)) 75 | 76 | if not hasattr(jenni.config, "last_strawpoll"): 77 | jenni.config.last_strawpoll = {} 78 | 79 | channel = input.sender 80 | jenni.config.last_strawpoll[channel] = link 81 | 82 | jenni.say(link) 83 | 84 | strawpoll.commands = ['straw', 'strawpoll'] 85 | strawpoll.priority = 'medium' 86 | strawpoll.example = '.strawpoll -t "Title Here" "Option 1" "Option 2" "Option 3"' 87 | 88 | def resend_strawpoll(jenni, input): 89 | """.resendstrawpoll - Resends the last strawpoll link created""" 90 | if hasattr(jenni.config, "last_strawpoll"): 91 | channel = input.sender 92 | if channel in jenni.config.last_strawpoll: 93 | return jenni.say(jenni.config.last_strawpoll[channel]) 94 | jenni.say("No Strawpoll links have been created yet.") 95 | 96 | resend_strawpoll.commands = ['rsp', 'restraw', 'resendstrawpoll'] 97 | resend_strawpoll.priority = 'low' 98 | resend_strawpoll.example = '.rsp' 99 | 100 | if __name__ == "__main__": 101 | print __doc__.strip() 102 | -------------------------------------------------------------------------------- /modules/tell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | tell.py - jenni Tell and Ask Module 4 | Copyright 2012-2013, yano (yanovich.net) 5 | Copyright 2008, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import os, re, time, random 14 | import threading 15 | 16 | maximum = 4 17 | 18 | 19 | def loadReminders(fn, lock): 20 | lock.acquire() 21 | try: 22 | result = {} 23 | f = open(fn) 24 | for line in f: 25 | line = line.strip() 26 | if line: 27 | try: tellee, teller, verb, timenow, msg = line.split('\t', 4) 28 | except ValueError: continue # @@ hmm 29 | result.setdefault(tellee, []).append((teller, verb, timenow, msg)) 30 | f.close() 31 | finally: 32 | lock.release() 33 | return result 34 | 35 | 36 | def dumpReminders(fn, data, lock): 37 | lock.acquire() 38 | try: 39 | f = open(fn, 'w') 40 | for tellee in data.iterkeys(): 41 | for remindon in data[tellee]: 42 | line = '\t'.join((tellee,) + remindon) 43 | try: f.write(line + '\n') 44 | except IOError: break 45 | try: f.close() 46 | except IOError: pass 47 | finally: 48 | lock.release() 49 | return True 50 | 51 | 52 | def setup(self): 53 | fn = self.nick + '-' + self.config.host + '.tell.db' 54 | self.tell_filename = os.path.join(os.path.expanduser('~/.jenni'), fn) 55 | if not os.path.exists(self.tell_filename): 56 | try: f = open(self.tell_filename, 'w') 57 | except OSError: pass 58 | else: 59 | f.write('') 60 | f.close() 61 | self.tell_lock = threading.Lock() 62 | self.reminders = loadReminders(self.tell_filename, self.tell_lock) # @@ tell 63 | 64 | 65 | def f_remind(jenni, input): 66 | teller = input.nick 67 | 68 | #if hasattr(jenni.config, 'logchan_pm'): 69 | #jenni.msg(jenni.config.logchan_pm, 'TELL used by %s in %s: %s' % (str(input.nick), str(input.sender), input)) 70 | 71 | #if not input.group(2) or input.group(3): 72 | if not input.group(2): 73 | return jenni.say('Please tell me who and what to tell people.') 74 | 75 | # @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15 76 | line_prefix = (input.group()).lower() 77 | if input.group() and (line_prefix.startswith('.tell') or line_prefix.startswith('.yell')): 78 | verb = 'tell'.encode('utf-8') 79 | line = input.groups() 80 | line_txt = line[1].split() 81 | tellee = line_txt[0] 82 | msg = ' '.join(line_txt[1:]) 83 | if line_prefix.startswith('.yell'): 84 | msg = (msg).upper() 85 | else: 86 | verb, tellee, msg = input.groups() 87 | 88 | ## handle unicode 89 | verb = verb.encode('utf-8') 90 | tellee = tellee.encode('utf-8') 91 | msg = msg.encode('utf-8') 92 | 93 | tellee = tellee.rstrip('.,:;') 94 | 95 | if not os.path.exists(jenni.tell_filename): 96 | return 97 | 98 | timenow = time.strftime('%d %b %H:%MZ', time.gmtime()) 99 | whogets = list() 100 | for tellee in tellee.split(','): 101 | if len(tellee) > 20: 102 | jenni.say('Nickname %s is too long.' % (tellee)) 103 | continue 104 | if not tellee.lower() in (teller.lower(), jenni.nick): # @@ 105 | jenni.tell_lock.acquire() 106 | try: 107 | if not tellee.lower() in whogets: 108 | whogets.append(tellee) 109 | if tellee not in jenni.reminders: 110 | jenni.reminders[tellee] = [(teller, verb, timenow, msg)] 111 | else: 112 | jenni.reminders[tellee].append((teller, verb, timenow, msg)) 113 | finally: 114 | jenni.tell_lock.release() 115 | response = str() 116 | if teller.lower() == tellee.lower() or tellee.lower() == 'me': 117 | response = 'You can %s yourself that.' % (verb) 118 | elif tellee.lower() == jenni.nick.lower(): 119 | response = "Hey, I'm not as stupid as Monty you know!" 120 | else: 121 | response = "I'll pass that on when %s is around." 122 | if len(whogets) > 1: 123 | listing = ', '.join(whogets[:-1]) + ', or ' + whogets[-1] 124 | response = response % (listing) 125 | elif len(whogets) == 1: 126 | response = response % (whogets[0]) 127 | else: 128 | return jenni.say('Huh?') 129 | 130 | if not whogets: # Only get cute if there are not legits 131 | rand = random.random() 132 | if rand > 0.9999: response = 'yeah, yeah' 133 | elif rand > 0.999: response = 'yeah, sure, whatever' 134 | 135 | jenni.reply(response) 136 | 137 | dumpReminders(jenni.tell_filename, jenni.reminders, jenni.tell_lock) # @@ tell 138 | f_remind.rule = ('$nick', ['[tTyY]ell', '[aA]sk'], r'(\S+) (.*)') 139 | f_remind.commands = ['tell', 'to', 'yell'] 140 | 141 | 142 | def getReminders(jenni, channel, key, tellee): 143 | lines = [] 144 | template = '%s: %s <%s> %s %s %s' 145 | today = time.strftime('%d %b', time.gmtime()) 146 | 147 | jenni.tell_lock.acquire() 148 | 149 | try: 150 | for (teller, verb, datetime, msg) in jenni.reminders[key]: 151 | if datetime.startswith(today): 152 | datetime = datetime[len(today) + 1:] 153 | lines.append(template % (tellee, datetime, teller, verb, tellee, msg)) 154 | 155 | try: del jenni.reminders[key] 156 | except KeyError: jenni.msg(channel, 'Er...') 157 | finally: 158 | jenni.tell_lock.release() 159 | 160 | return lines 161 | 162 | 163 | def message(jenni, input): 164 | #if not input.sender.startswith('#'): return 165 | 166 | tellee = input.nick 167 | channel = input.sender 168 | 169 | if not os: return 170 | if not os.path.exists(jenni.tell_filename): 171 | return 172 | 173 | reminders = [] 174 | remkeys = list(reversed(sorted(jenni.reminders.keys()))) 175 | for remkey in remkeys: 176 | if not remkey.endswith('*') or remkey.endswith(':'): 177 | if tellee.lower() == remkey.lower(): 178 | reminders.extend(getReminders(jenni, channel, remkey, tellee)) 179 | elif tellee.lower().startswith(remkey.rstrip('*:').lower()): 180 | reminders.extend(getReminders(jenni, channel, remkey, tellee)) 181 | 182 | for line in reminders[:maximum]: 183 | jenni.say(line) 184 | 185 | if reminders[maximum:]: 186 | jenni.say('Further messages sent privately') 187 | for line in reminders[maximum:]: 188 | jenni.msg(tellee, line) 189 | 190 | if len(jenni.reminders.keys()) != remkeys: 191 | dumpReminders(jenni.tell_filename, jenni.reminders, jenni.tell_lock) # @@ tell 192 | message.rule = r'(.*)' 193 | message.priority = 'low' 194 | 195 | if __name__ == '__main__': 196 | print __doc__.strip() 197 | -------------------------------------------------------------------------------- /modules/tld.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | tld.py - jenni Why Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import BeautifulSoup 13 | import datetime as dt 14 | import re 15 | import urllib2 16 | import web 17 | 18 | BS = BeautifulSoup.BeautifulSoup 19 | 20 | uri = 'https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains' 21 | r_tag = re.compile(r'<(?!!)[^>]+>') 22 | r_quote = re.compile(r'\[.*?\]') 23 | 24 | soup = False 25 | last_updated = dt.datetime.now() - dt.timedelta(days=30) 26 | 27 | 28 | def gettld(jenni, input): 29 | '''.tld .sh -- displays information about a given top level domain.''' 30 | global soup 31 | 32 | text = input.group(2) 33 | if not text: 34 | jenni.reply("You didn't provide any input.") 35 | return 36 | text = text.split()[0] 37 | if text and text.startswith('.'): 38 | text = text[1:] 39 | text = text.encode('utf-8') 40 | 41 | if (dt.datetime.now() - last_updated) >= dt.timedelta(days=14): 42 | ## reloads cache if data becomes too old 43 | reload_cache() 44 | 45 | if not soup: 46 | reload_cache() 47 | 48 | tlds = soup.findAll('tr', {'valign': 'top'}) 49 | for tld in tlds: 50 | tld_tds = tld('td') 51 | out = dict() 52 | if not text.startswith('.'): 53 | text = '.' + text 54 | if len(tld_tds[0]('a')) > 0 and tld_tds[0]('a')[0].text == text: 55 | if tld_tds[1]('a') and tld_tds[1]('img'): 56 | out['entity'] = tld_tds[1]('a')[0].text 57 | else: 58 | out['entity'] = tld_tds[1].text 59 | none_avail = 'N/A' 60 | out['expl'] = none_avail 61 | if tld_tds[2].text: 62 | out['expl'] = tld_tds[2].text 63 | out['notes'] = none_avail 64 | out['idn'] = none_avail 65 | out['dnssec'] = none_avail 66 | out['sld'] = none_avail 67 | out['ipv6'] = none_avail 68 | 69 | if len(tld_tds) >= 7: 70 | out['notes'] = str(tld_tds[3]) 71 | out['idn'] = str(tld_tds[4].text) 72 | out['dnssec'] = str(tld_tds[5].text) 73 | out['sld'] = str(tld_tds[6].text) 74 | if len(tld_tds) == 8: 75 | out['ipv6'] = str(tld_tds[7].text) 76 | elif len(tld_tds) == 5: 77 | out['idn'] = str(tld_tds[3].text) 78 | out['dnssec'] = str(tld_tds[4].text) 79 | 80 | new_out = dict() 81 | for x in out: 82 | chomped = r_tag.sub('', out[x].strip()) 83 | chomped = r_quote.sub('', chomped) 84 | if chomped == ' ': 85 | chomped = none_avail 86 | try: 87 | chomped = (chomped).encode('utf-8') 88 | except: 89 | pass 90 | chomped = (chomped).decode('utf-8') 91 | new_out[x] = chomped 92 | 93 | return jenni.say('Entity: %s (Explanation: %s, Notes: %s). IDN: %s, DNSSEC: %s, SLD: %s, IPv6: %s' % (new_out['entity'], new_out['expl'], new_out['notes'], new_out['idn'], new_out['dnssec'], new_out['sld'], new_out['ipv6'])) 94 | 95 | return jenni.say('No matches found for TLD: %s' % (text)) 96 | 97 | gettld.commands = ['tld'] 98 | gettld.example = '.tld .it' 99 | 100 | 101 | def reload_cache(): 102 | global last_updated 103 | global soup 104 | 105 | last_updated = dt.datetime.now() 106 | page = web.get(uri) 107 | soup = BS(page) 108 | 109 | 110 | def tld_cache(jenni, input): 111 | jenni.say('TLD cache from Wikipedia last updated: ' + str(last_updated)) 112 | 113 | reload_cache() 114 | 115 | jenni.say('TLD cache is now current.') 116 | tld_cache.commands = ['tld-cache'] 117 | 118 | 119 | if __name__ == '__main__': 120 | print __doc__.strip() 121 | -------------------------------------------------------------------------------- /modules/twitter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | twitter.py - jenni Twitter Module 4 | Copyright 2012-2013, yano (yanovich.net) 5 | Copyright 2012-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | from htmlentitydefs import name2codepoint 14 | import json 15 | import oauth2 as oauth 16 | import re 17 | import time 18 | import urllib 19 | import urllib2 20 | import web 21 | 22 | from modules import unicode as uc 23 | 24 | r_username = re.compile(r'^[a-zA-Z0-9_]{1,15}$') 25 | r_link = re.compile(r'^https?://twitter.com/\S+$') 26 | r_p = re.compile(r'(?ims)(

    )') 27 | r_tag = re.compile(r'(?ims)<[^>]+>') 28 | r_anchor = re.compile(r'(?ims)()') 29 | r_expanded = re.compile(r'(?ims)data-expanded-url=["\'](.*?)["\']') 30 | r_whiteline = re.compile(r'(?ims)[ \t]+[\r\n]+') 31 | r_breaks = re.compile(r'(?ims)[\r\n]+') 32 | r_entity = re.compile(r'&[A-Za-z0-9#]+;') 33 | 34 | request_token_url = 'https://twitter.com/oauth/request_token' 35 | access_token_url = 'https://twitter.com/oauth/access_token' 36 | authorize_url = 'https://twitter.com/oauth/authorize' 37 | 38 | 39 | def initialize_keys(jenni, consumer_key='', consumer_secret=''): 40 | global client 41 | if not hasattr(jenni.config, 'twitter_consumer_key') or\ 42 | not hasattr(jenni.config, 'twitter_consumer_secret'): 43 | return False, "Please sign up for Twitter's API and provide the keys in the configuration." 44 | consumer_key = jenni.config.twitter_consumer_key 45 | consumer_secret = jenni.config.twitter_consumer_secret 46 | 47 | try: 48 | consumer = oauth.Consumer(consumer_key, consumer_secret) 49 | client = oauth.Client(consumer) 50 | except: 51 | return False, "Could not initailize Twitter OAuth connection." 52 | 53 | return True, '' 54 | 55 | 56 | def entity(*args, **kargs): 57 | return web.entity(*args, **kargs).encode('utf-8') 58 | 59 | 60 | def decode(html): 61 | return web.r_entity.sub(entity, html) 62 | 63 | 64 | def expand(tweet): 65 | def replacement(match): 66 | anchor = match.group(1) 67 | for link in r_expanded.findall(anchor): 68 | return link 69 | return r_tag.sub('', anchor) 70 | return r_anchor.sub(replacement, tweet) 71 | 72 | def e(m): 73 | entity = m.group() 74 | if entity.startswith('&#x'): 75 | cp = int(entity[3:-1], 16) 76 | meep = unichr(cp) 77 | elif entity.startswith('&#'): 78 | cp = int(entity[2:-1]) 79 | meep = unichr(cp) 80 | else: 81 | char = name2codepoint[entity[1:-1]] 82 | meep = unichr(char) 83 | try: 84 | return uc.decode(meep) 85 | except: 86 | return uc.decode(uc.encode(meep)) 87 | 88 | def fixURLs(jcont): 89 | out_text = jcont['text'] 90 | list_of_urls = jcont['entities']['urls'] 91 | for url in list_of_urls: 92 | #print url['url'] 93 | if url['url'] in out_text: 94 | out_text = out_text.replace(url['url'], url['expanded_url']) 95 | if 'extended_entities' in jcont: 96 | list_of_more_urls = jcont['extended_entities']['media'] 97 | for url in list_of_more_urls: 98 | if url['url'] in out_text: 99 | out_text = out_text.replace(url['url'], url['expanded_url']) 100 | return out_text 101 | 102 | def remove_spaces(x): 103 | if ' ' in x: 104 | x = x.replace(' ', ' ') 105 | return remove_spaces(x) 106 | else: 107 | return x 108 | 109 | 110 | def fetchbyID(term): 111 | global client 112 | resp, content = client.request('https://api.twitter.com/1.1/statuses/show.json?id=' + urllib2.quote(term), 'GET') 113 | if resp['status'] != '200': 114 | return 'Could not reach Twitter API.' 115 | return content 116 | 117 | 118 | def fetchbyUserName(term): 119 | global client 120 | resp, content = client.request("https://api.twitter.com/1.1/statuses/user_timeline.json?count=1&screen_name=" + term, "GET") 121 | if resp['status'] == '401': # tweets are private 122 | return '{0}\'s tweets are not public.'.format(term) 123 | if resp['status'] != '200': 124 | return 'Could not reach Twitter API.' 125 | try: 126 | json_content = json.loads(content) 127 | except: 128 | return 'Could not make sense of data from Twitter API.' 129 | 130 | #return format_tweet(json_content[0]) 131 | return json.dumps(json_content[0]) 132 | 133 | 134 | def format_tweet(content): 135 | txt = fixURLs(content) 136 | #txt = uc.encode(txt) 137 | txt = expand(txt) 138 | txt = txt.strip() 139 | #txt = uc.decode(txt) 140 | #txt = uc.encode(txt) 141 | #txt = uc.decode(txt) 142 | txt = r_entity.sub(e, txt) 143 | txt = r_whiteline.sub(' ', txt) 144 | txt = r_breaks.sub(' ', txt) 145 | txt = decode(txt) 146 | txt = remove_spaces(txt) 147 | txt = txt.replace('http://twitter.c', 'https://twitter.c') 148 | 149 | posted = content['created_at'] 150 | fav_count = content['favorite_count'] 151 | rt_count = content['retweet_count'] 152 | name = content['user']['screen_name'] 153 | 154 | return u'{1} | By: @{0}, Date: {2}, RT#: {3}, Favs: {4}'.format(name, txt, posted, rt_count, fav_count) 155 | 156 | 157 | def call_twitter(query): 158 | ans = None 159 | 160 | params = query.strip().split() 161 | 162 | if len(params) == 1: 163 | # Only one parameter. By default a username. 164 | # Also allow a twitter URL or an id number 165 | if query.startswith('http'): 166 | ## example: https://twitter.com/username/status/46611258765606912 167 | m = re.match(r'https?://(?:www\.)?twitter\.com/\w*/status/(\d+)', query) 168 | if not m: 169 | return 'Could not parse twitter url.' 170 | return fetchbyID(m.group(1)) 171 | 172 | if query.isdigit(): 173 | return fetchbyID(query) 174 | else: 175 | return fetchbyUserName(query) 176 | 177 | elif len(params) == 2: 178 | # two params means user and status id, which is the same as 179 | # just status id 180 | return fetchbyID(params[1]) 181 | 182 | else: 183 | return '??' 184 | 185 | 186 | def twitter(jenni, input): 187 | status, response = initialize_keys(jenni) 188 | if not status: 189 | return jenni.say(response) 190 | 191 | arg = input.group(2) 192 | 193 | if not arg: 194 | if hasattr(jenni, 'last_seen_uri') and input.sender in jenni.last_seen_uri: 195 | temp = jenni.last_seen_uri[input.sender] 196 | if '//twitter.com' in temp: 197 | arg = temp 198 | else: 199 | return jenni.say('Last link seen in this channel is not from twitter.com.') 200 | else: 201 | return jenni.reply('Give me a link, a username, or a tweet id.') 202 | 203 | page = call_twitter(arg) 204 | 205 | try: 206 | json_page = json.loads(page) 207 | except: 208 | return jenni.say(str(page)) 209 | if 'user' in json_page and 'protected' in json_page['user'] and json_page['user']['protected']: 210 | return jenni.say('This user has their tweets "protected."') 211 | 212 | if 'retweeted_status' in json_page: 213 | out = u'RT: ' 214 | out += format_tweet(json_page['retweeted_status']) 215 | else: 216 | out = format_tweet(json_page) 217 | return jenni.say(out) 218 | 219 | twitter.commands = ['tw', 'twitter'] 220 | twitter.thread = True 221 | 222 | if __name__ == '__main__': 223 | print __doc__ 224 | -------------------------------------------------------------------------------- /modules/twss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | twss.py - jenni's That's What She Said Module 4 | Copyright 2011 - Joel Friedly and Matt Meinwald 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | 11 | This module detects common phrases that many times can be responded with 12 | "That's what she said." 13 | 14 | It also allows users to add new "that's what she said" jokes to it's library 15 | by following any appropriate statement with ".twss". 16 | """ 17 | 18 | import urllib2 19 | import re 20 | import os 21 | import sys 22 | 23 | 24 | last = "DEBUG_ME" # if you see this in the terminal, something broke. 25 | 26 | if not os.path.exists("modules/twss.txt"): 27 | f = open("modules/twss.txt", "w") 28 | url = "http://www.twssstories.com/best?page=" 29 | first_re = re.compile(r"

    .+TWSS\.*

    ") 30 | inner_re = re.compile(r'".+"') 31 | url2 = "http://www.shesaidit.ca/index.php?pageno=" 32 | second_re = re.compile(r'"style30">.*') 33 | 34 | print "Now creating TWSS database. This will take a few minutes.", 35 | for page in range(1,148): 36 | sys.stdout.flush() 37 | print ".", 38 | curr_url = url + str(page) 39 | html = urllib2.urlopen(curr_url) 40 | story_list = first_re.findall(html.read()) 41 | for story in story_list: 42 | if len(inner_re.findall(story)) > 0: 43 | lowercase = inner_re.findall(story)[0].lower() 44 | f.write(re.sub("[^\w\s]", "", lowercase) + "\n") 45 | 46 | for page in range(1,146): 47 | sys.stdout.flush() 48 | print ".", 49 | curr_url = url2 + str(page) 50 | html = urllib2.urlopen(curr_url) 51 | matches_list = second_re.findall(html.read()) 52 | for match in matches_list: 53 | lowercase = match[10:-7].lower().strip() 54 | if len(inner_re.findall(lowercase)) > 0: 55 | lowercase = inner_re.findall(lowercase)[0] 56 | f.write(re.sub("[^\w\s]", "", lowercase) + "\n") 57 | f.close() 58 | 59 | 60 | def say_it(jenni, input): 61 | global last 62 | user_quotes = None 63 | with open("modules/twss.txt") as f: 64 | scraped_quotes = frozenset([line.rstrip() for line in f]) 65 | if os.path.exists("modules/twss_user_added.txt"): 66 | with open("modules/twss_user_added.txt") as f2: 67 | user_quotes = frozenset([line.rstrip() for line in f2]) 68 | quotes = scraped_quotes.union(user_quotes) if user_quotes else scraped_quotes 69 | formatted = input.group(1).lower() 70 | if re.sub("[^\w\s]", "", formatted) in quotes: 71 | jenni.say("That's what she said.") 72 | last = re.sub("[^\w\s]", "", formatted) 73 | say_it.rule = r"(.*)" 74 | say_it.priority = "low" 75 | say_it.rate = 20 76 | 77 | def add_twss(jenni, input): 78 | txt = input.group(2) 79 | if txt: 80 | with open("modules/twss_user_added.txt", "a") as f: 81 | f.write(re.sub(r"[^\w\s]", "", last.lower()) + "\n") 82 | f.close() 83 | jenni.say("Added, new \"That's what she said.\"") 84 | add_twss.commands = ["twss"] 85 | add_twss.priority = "low" 86 | add_twss.threading = False 87 | add_twss.rate = 20 88 | 89 | if __name__ == '__main__': 90 | print __doc__.strip() 91 | -------------------------------------------------------------------------------- /modules/unicode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | unicode.py - jenni Unicode Module 4 | Copyright 2010-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import re 13 | import unicodedata 14 | import urlparse 15 | 16 | control_chars = ''.join(map(unichr, range(0,32) + range(127,160))) 17 | control_char_re = re.compile(u'[%s]' % re.escape(control_chars)) 18 | 19 | 20 | def supercombiner(jenni, input): 21 | """.sc -- displays the infamous supercombiner""" 22 | s = 'u' 23 | for i in xrange(1, 3000): 24 | if unicodedata.category(unichr(i)) == "Mn": 25 | s += unichr(i) 26 | if len(s) > 100: 27 | break 28 | jenni.say(s) 29 | supercombiner.commands = ['sc'] 30 | supercombiner.rate = 30 31 | 32 | 33 | def decode(bit): 34 | try: 35 | if isinstance(bit, str) or isinstance(bit, unicode): 36 | text = bit.decode('utf-8') 37 | else: 38 | text = str() 39 | except UnicodeDecodeError: 40 | try: 41 | text = bit.decode('iso-8859-1') 42 | except UnicodeDecodeError: 43 | text = bit.decode('cp1252') 44 | return text 45 | 46 | 47 | def encode(bit): 48 | try: 49 | if isinstance(bit, str) or isinstance(bit, unicode): 50 | text = bit.encode('utf-8') 51 | else: 52 | text = str() 53 | except UnicodeEncodeError: 54 | try: 55 | text = bit.encode('iso-8859-1') 56 | except UnicodeEncodeError: 57 | text = bit.encode('cp1252') 58 | return text 59 | 60 | 61 | def urlEncodeNonAscii(b): 62 | return re.sub('[\x80-\xFF]', lambda c: '%%%02x' % ord(c.group(0)), b) 63 | 64 | 65 | def iriToUri(iri): 66 | parts = urlparse.urlparse(iri) 67 | return urlparse.urlunparse( 68 | part.encode('idna') if parti == 1 else urlEncodeNonAscii( 69 | part.encode('utf-8')) 70 | for parti, part in enumerate(parts) 71 | ) 72 | 73 | 74 | def remove_control_chars(s): 75 | return control_char_re.sub('', s) 76 | 77 | 78 | if __name__ == '__main__': 79 | print __doc__.strip() 80 | -------------------------------------------------------------------------------- /modules/unostats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | unostats.py -- jenni's uno stat generator 4 | Copyright 2011-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | def rankings (ranktype): 13 | from copy import copy 14 | prescores = [ ] 15 | try: 16 | f = open ("unoscores.txt", 'r') 17 | for l in f: 18 | t = l.replace ('\n', '').split (' ') 19 | if len (t) < 4: continue 20 | prescores.append (copy (t)) 21 | if len (t) == 4: t.append (0) 22 | f.close () 23 | except: pass 24 | #prescores = sorted (prescores, lambda x, y: cmp ((y[1] != '0') and (float (y[3]) / int (y[1])) or 0, (x[1] != '0') and (float (x[3]) / int (x[1])) or 0)) 25 | prescores = sorted ( prescores, lambda x, y: cmp ( (y[1] != '0') and ( ( float(y[3]) / int(y[1]) ) / ( 1.01 - ( float(y[2]) / int(y[1]) ) ) ) or 0, (x[1] != '0') and ( ( float(x[3]) / int(x[1]) ) / ( 1.01 - ( float(x[2]) / int(x[1]) ) ) ) or 0 ) ) 26 | 27 | return prescores 28 | 29 | def showstats (jenni, input): 30 | STRINGS = { 'SCORE_ROW' : '\x0300,01#%s %s (%s points, %s games, %s won, %.2f points per game, %.2f percent wins, %.2f A)' } 31 | text = input.group().split() 32 | prescores = rankings(text) 33 | i = 1 34 | c = text[1] 35 | 36 | if c.isdigit(): 37 | c = int(c) 38 | for z in prescores[:c]: 39 | jenni.msg(input.nick, STRINGS['SCORE_ROW'] % (i, z[0], z[3], z[1], z[2], float(z[3])/float(z[1]), float(z[2])/float(z[1])*100, ( float(z[3]) / int(z[1]) ) / ( 1.01 - ( float(z[2]) / int(z[1]) ) ))) 40 | # float(z[3]) / ( (1.01 - ( float(z[2]) / int(z[1] ) ) ) * int(z[1]) ) 41 | # ( float(z[3]) / int(z[1]) ) / ( 1.01 - ( float(z[2]) / int(z[1]) ) ) 42 | i += 1 43 | else: 44 | j = 1 45 | t = str(c) 46 | jenni.say(t) 47 | for y in prescores: 48 | if y[0] == t: 49 | jenni.msg(input.nick, STRINGS['SCORE_ROW'] % (j, y[0], y[3], y[1], y[2], float(y[3])/float(y[1]), float(y[2])/float(y[1])*100, ( float(y[3]) / int(y[1]) ) / ( 1.01 - ( float(y[2]) / int(y[1]) ) ))) 50 | j += 1 51 | 52 | showstats.commands = ['unostats2'] 53 | -------------------------------------------------------------------------------- /modules/validate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | validate.py - jenni Validation Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import web 14 | 15 | def val(jenni, input): 16 | """Check a webpage using the W3C Markup Validator.""" 17 | if not input.group(2): 18 | return jenni.reply("Nothing to validate.") 19 | uri = input.group(2) 20 | if not uri.startswith('http://'): 21 | uri = 'http://' + uri 22 | 23 | path = '/check?uri=%s;output=xml' % web.urllib.quote(uri) 24 | info = web.head('http://validator.w3.org' + path) 25 | 26 | result = uri + ' is ' 27 | 28 | if isinstance(info, list): 29 | return jenni.say('Got HTTP response %s' % info[1]) 30 | 31 | if info.has_key('X-W3C-Validator-Status'): 32 | result += str(info['X-W3C-Validator-Status']) 33 | if info['X-W3C-Validator-Status'] != 'Valid': 34 | if info.has_key('X-W3C-Validator-Errors'): 35 | n = int(info['X-W3C-Validator-Errors'].split(' ')[0]) 36 | if n != 1: 37 | result += ' (%s errors)' % n 38 | else: result += ' (%s error)' % n 39 | else: result += 'Unvalidatable: no X-W3C-Validator-Status' 40 | 41 | jenni.reply(result) 42 | val.rule = (['val'], r'(?i)(\S+)') 43 | val.example = '.val http://www.w3.org/' 44 | 45 | if __name__ == '__main__': 46 | print __doc__.strip() 47 | -------------------------------------------------------------------------------- /modules/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | version.py - jenni Version Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2009-2013, Silas Baronda 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | from datetime import datetime 14 | from subprocess import * 15 | 16 | 17 | def git_info(): 18 | p = Popen(['git', 'log', '-n 1'], stdout=PIPE, close_fds=True) 19 | 20 | commit = p.stdout.readline() 21 | author = p.stdout.readline() 22 | date = p.stdout.readline() 23 | return commit, author, date 24 | 25 | 26 | def version(jenni, input): 27 | commit, author, date = git_info() 28 | 29 | jenni.say(str(input.nick) + ': running version:') 30 | jenni.say(' ' + commit) 31 | jenni.say(' ' + author) 32 | jenni.say(' ' + date) 33 | version.commands = ['version'] 34 | version.priority = 'medium' 35 | version.rate = 10 36 | 37 | 38 | def ctcp_version(jenni, input): 39 | commit, author, date = git_info() 40 | date = date.replace(' ', '') 41 | 42 | jenni.write(('NOTICE', input.nick), 43 | '\x01VERSION {0} : {1}\x01'.format(commit, date)) 44 | ctcp_version.rule = '\x01VERSION\x01' 45 | ctcp_version.rate = 20 46 | 47 | 48 | def ctcp_source(jenni, input): 49 | jenni.write(('NOTICE', input.nick), 50 | '\x01SOURCE https://github.com/myano/jenni/\x01') 51 | jenni.write(('NOTICE', input.nick), 52 | '\x01SOURCE\x01') 53 | ctcp_source.rule = '\x01SOURCE\x01' 54 | ctcp_source.rate = 10 55 | 56 | 57 | def ctcp_ping(jenni, input): 58 | text = input.group() 59 | text = text.replace('PING ', '') 60 | text = text.replace('\x01', '') 61 | jenni.write(('NOTICE', input.nick), 62 | '\x01PING {0}\x01'.format(text)) 63 | ctcp_ping.rule = '\x01PING\s(.*)\x01' 64 | ctcp_ping.rate = 10 65 | 66 | 67 | def ctcp_time(jenni, input): 68 | dt = datetime.now() 69 | current_time = dt.strftime('%A, %d. %B %Y %I:%M%p') 70 | jenni.write(('NOTICE', input.nick), 71 | '\x01TIME {0}\x01'.format(current_time)) 72 | ctcp_time.rule = '\x01TIME\x01' 73 | ctcp_time.rate = 10 74 | 75 | if __name__ == '__main__': 76 | print __doc__.strip() 77 | -------------------------------------------------------------------------------- /modules/why.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | why.py - jenni Why Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Licensed under the Eiffel Forum License 2. 6 | 7 | More info: 8 | * jenni: https://github.com/myano/jenni/ 9 | * Phenny: http://inamidst.com/phenny/ 10 | """ 11 | 12 | import re 13 | import web 14 | 15 | whyuri = 'http://www.leonatkinson.com/random/index.php/rest.html?method=advice' 16 | r_paragraph = re.compile(r'.*?') 17 | 18 | 19 | def getwhy(jenni, input): 20 | page = web.get(whyuri) 21 | paragraphs = r_paragraph.findall(page) 22 | out = str() 23 | if paragraphs: 24 | line = re.sub(r'<[^>]*?>', '', unicode(paragraphs[0])) 25 | out = line.lower().capitalize() + "." 26 | else: 27 | out = 'We are unable to find any reasons *why* this should work.' 28 | 29 | return jenni.say(out) 30 | getwhy.commands = ['why', 'tubbs'] 31 | getwhy.thread = False 32 | getwhy.rate = 30 33 | 34 | if __name__ == '__main__': 35 | print __doc__.strip() 36 | -------------------------------------------------------------------------------- /modules/wikipedia.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | wikipedia.py - jenni Wikipedia Module 4 | Copyright 2009-2013, yano (yanovich.net) 5 | Copyright 2008-2013, Sean B. Palmer (inamidst.com) 6 | Licensed under the Eiffel Forum License 2. 7 | 8 | More info: 9 | * jenni: https://github.com/myano/jenni/ 10 | * Phenny: http://inamidst.com/phenny/ 11 | """ 12 | 13 | import re, urllib, gzip, StringIO 14 | import web 15 | 16 | wikiuri = 'https://%s.wikipedia.org/wiki/%s' 17 | # wikisearch = 'http://%s.wikipedia.org/wiki/Special:Search?' \ 18 | # + 'search=%s&fulltext=Search' 19 | 20 | r_tr = re.compile(r'(?ims)]*>.*?') 21 | r_paragraph = re.compile(r'(?ims)]*>.*?

    |]*>.*?') 22 | r_tag = re.compile(r'<(?!!)[^>]+>') 23 | r_whitespace = re.compile(r'[\t\r\n ]+') 24 | r_empty = re.compile(r'\S+') 25 | #