├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bot.py ├── config.default ├── core ├── config.py ├── db.py ├── irc.py ├── main.py └── reload.py ├── install.md ├── plugins ├── 4chan.py ├── _broken │ ├── anime.py │ ├── answers.py │ ├── chatbot.py │ ├── mylifeisaverage.py │ ├── rss.py │ ├── snopes.py │ └── stock.py ├── _disabled │ ├── bf.py │ ├── convert.py │ ├── correction.py │ ├── domains.py │ ├── dotnetpad.py │ ├── explain.py │ ├── fact.py │ ├── fishbans.py │ ├── gainz.py │ ├── gitio.py │ ├── isdown.py │ ├── mcitems.py │ ├── mctools.py │ ├── mcwiki.py │ ├── migrate.py │ ├── mtg.py │ ├── munge.py │ ├── oblique.py │ ├── perl.py │ ├── ping.py │ ├── poll.py │ ├── pomf.py │ ├── pre.py │ ├── pyexec.py │ ├── repaste.py │ ├── request.py │ ├── rottentomatoes.py │ ├── somethingawful.py │ ├── steam.py │ ├── suggest.py │ ├── time.py │ ├── title.py │ ├── touhouradio.py │ ├── urlhistory.py │ ├── valvesounds.py │ ├── vote.py │ ├── wordoftheday.py │ └── yandere.py ├── _unused │ ├── core_db.py │ ├── exhentai.py │ ├── lmgtfy.py │ ├── rottentomatoes.py │ ├── shorten.py │ ├── steam_calc.py │ └── tvdb.py ├── ai.py ├── amazon.py ├── bash.py ├── booru.py ├── calculator.py ├── choose.py ├── coin.py ├── coins.py ├── core_admin_channel.py ├── core_admin_global.py ├── core_ctcp.py ├── core_misc.py ├── core_sieve.py ├── core_user.py ├── countdown.py ├── cypher.py ├── data │ ├── 8ball_responses.txt │ ├── coingecko-coins.json │ ├── flirts.txt │ ├── fortunes.txt │ ├── gainz.txt │ ├── help.txt │ ├── insults.txt │ ├── itemids.txt │ ├── keks.txt │ ├── kills.json │ ├── kills.txt │ ├── larts.txt │ ├── lewd.txt │ ├── lewds.txt │ ├── lolis.txt │ ├── moists.txt │ ├── name_files │ │ ├── dragons.json │ │ ├── dwarves.json │ │ ├── elves_female.json │ │ ├── elves_male.json │ │ ├── fantasy.json │ │ ├── female.json │ │ ├── general.json │ │ ├── hobbits.json │ │ ├── inns.json │ │ ├── items.json │ │ ├── male.json │ │ ├── narn.json │ │ └── warrior_cats.json │ ├── old.txt │ ├── potato.txt │ ├── qts.txt │ ├── recipes.txt │ ├── slap_items.txt │ ├── slaps.json │ ├── slaps.txt │ ├── slogans.txt │ ├── smileys.txt │ ├── troll.txt │ ├── trolls.txt │ ├── urmom.txt │ ├── yiffs.txt │ └── youtube.txt ├── datafiles.py ├── debt.py ├── dice.py ├── dictionary.py ├── distance.py ├── distro.py ├── duckduckgo.py ├── fmylife.py ├── furry.py ├── gelbooru.py ├── geoip.py ├── google.py ├── hashtags.py ├── heartbleed.py ├── help.py ├── kernel.py ├── lastfm.py ├── log.py ├── lyrics.py ├── major_league_baseball.py ├── masshighlight.py ├── media.py ├── mediawiki.py ├── metacritic.py ├── mygengo_translate.py ├── namegen.py ├── newegg.py ├── nfl.py ├── quote.py ├── radio.py ├── religion.py ├── reminder.py ├── seen.py ├── soundcloud.py ├── spellcheck.py ├── spotify.py ├── steam.py ├── stock.py ├── stupid.py ├── system.py ├── tell.py ├── times.py ├── todo.py ├── translate.py ├── twitch.py ├── twitter.py ├── urbandict.py ├── urls.py ├── util │ ├── __init__.py │ ├── database.py │ ├── execute.py │ ├── formatting.py │ ├── hook.py │ ├── http.py │ ├── scheduler.py │ ├── text.py │ ├── textgen.py │ ├── timeformat.py │ ├── timesince.py │ ├── urlnorm.py │ ├── user.py │ └── web.py ├── utilities │ ├── __init__.py │ ├── formatting.py │ ├── iterable.py │ ├── request.py │ └── services.py ├── utility.py ├── validate.py ├── vimeo.py ├── weather.py ├── wolframalpha.py ├── wordoftheday.py └── youtube.py ├── requirements.txt ├── requirements_broken.txt ├── requirements_extra.txt └── uguubot /.gitignore: -------------------------------------------------------------------------------- 1 | config 2 | persist 3 | channelconfig 4 | _delete 5 | gitflow 6 | *.db 7 | *.log 8 | .*.swp 9 | *.pyc 10 | *.orig 11 | *.tmp 12 | .project 13 | .pydevproject 14 | .geany 15 | *.sublime-project 16 | *.sublime-workspace 17 | .DS_Store 18 | cmdflood 19 | flood 20 | *.fuse* 21 | None.txt 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk add \ 3 | python2 \ 4 | gcc \ 5 | g++ \ 6 | python2-dev \ 7 | libxml2 \ 8 | libxml2-dev \ 9 | libxslt-dev \ 10 | enchant2 \ 11 | enchant2-dev && \ 12 | python2 -m ensurepip 13 | 14 | WORKDIR /home/taigabot 15 | COPY ./requirements.txt ./requirements.txt 16 | RUN python2 -m pip install -r requirements.txt 17 | COPY ./requirements_extra.txt ./requirements_extra.txt 18 | RUN python2 -m pip install -r requirements_extra.txt 19 | COPY ./ ./ 20 | 21 | CMD [ "python2", "./bot.py" ] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taigabot 2 | 3 | * Easy to use wrapper 4 | * Intuitive configuration 5 | * Fully controlled from IRC 6 | * Fully compatable with existing skybot plugins 7 | * Easily extendable 8 | * Thorough documentation 9 | * Cross-platform 10 | * Muti-threaded, efficient 11 | * Automatic reloading 12 | * Little boilerplate 13 | 14 | ### Installation 15 | Taigabot runs only on Python 2.7. See [install.md](install.md#instructions) for [ubuntu](install.md#ubuntu) or [alpine](install.md#alpine) instructions. 16 | 17 | The biggest hurdle is `lxml` which needs a compiler and a bunch of libraries. 18 | 19 | #### Other dependencies 20 | Some commands require extra python packages, more information can be found on [install.md § specific dependencies](install.md#specific-dependencies). 21 | 22 | Some commands also require API keys, 23 | 24 | The system packages `daemon` or `screen` are recomended for the launcher to run optimally. 25 | 26 | 27 | ### Run 28 | Once you have installed the required dependencies, you need to create a config file: 29 | 30 | cp config.default config 31 | vim config 32 | python2 bot.py 33 | 34 | There are two ways you can run the bot: 35 | 36 | #### Launcher 37 | 38 | **Note:** Due to some issues with the launcher we recommend you run the bot manually as detailed below. 39 | 40 | **Note:** If migrating from an older version please look in the modules/Admin.py at the migrate_old_db command. MAKE SURE TO USE A COPY OF THE ORIGINAL DB. 41 | 42 | The launcher will start the bot as a background process, and allow the bot to close and restart itself. This is only supported on unix-like machines (not Windows). 43 | 44 | For the launcher to work properly, install `screen`, or `daemon` (daemon is recommended): 45 | 46 | `apt-get install screen` 47 | 48 | `apt-get install daemon` 49 | 50 | Once you have installed either `screen` or `daemon`, run the start command: 51 | 52 | `./uguubot start` 53 | 54 | It will generate a default config for you. Once you have edited the config, run it again with the same command: 55 | 56 | `./uguubot start` 57 | 58 | This will start up your bot as a background process. To stop it, use `./uguubot stop`.) 59 | 60 | #### Manually 61 | 62 | To manually run the bot and get console output, run it with: 63 | 64 | `python bot.py` 65 | 66 | (note: running the bot without the launcher breaks the start and restart commands) 67 | 68 | ## License 69 | 70 | UguuBot is **licensed** under the **GPL v3** license. The terms are as follows. 71 | 72 | UguuBot/DEV 73 | Copyright © 2013-2013 Infinity - 74 | Copyright © 2011-2012 Luke Rogers / ClouDev - <[cloudev.github.com](http://cloudev.github.com)> 75 | 76 | UguuBot is free software: you can redistribute it and/or modify 77 | it under the terms of the GNU General Public License as published by 78 | the Free Software Foundation, either version 3 of the License, or 79 | (at your option) any later version. 80 | 81 | UguuBot is distributed in the hope that it will be useful, 82 | but WITHOUT ANY WARRANTY; without even the implied warranty of 83 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 84 | GNU General Public License for more details. 85 | 86 | You should have received a copy of the GNU General Public License 87 | along with UguuBot. If not, see . 88 | 89 | ## Contact 90 | 91 | Need to contact someone? Head on over to #uguubot at irc.rizon.net for assistance or any other needs. 92 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "InfinityLabs" 4 | __authors__ = ["Infinity"] 5 | __copyright__ = "Copyright 2013, InfinityLabs" 6 | __copyright__ = "Copyright 2012, ClouDev" 7 | __credits__ = ["infinity","thenoodle", "_frozen", "rmmh"] 8 | __license__ = "GPL v3" 9 | __version__ = "DEV" 10 | __maintainer__ = "InfinityLabs" 11 | __email__ = "root@infinitylabs.us" 12 | __status__ = "Development" 13 | 14 | import os 15 | import Queue 16 | import sys 17 | import time 18 | import platform 19 | 20 | sys.path += ['plugins'] # so 'import hook' works without duplication 21 | sys.path += ['lib'] 22 | os.chdir(sys.path[0] or '.') # do stuff relative to the install directory 23 | 24 | 25 | class Bot(object): 26 | pass 27 | 28 | print 'UguuBot %s (%s) ' % (__version__, __status__) 29 | 30 | # print debug info 31 | opsys = platform.platform() 32 | python_imp = platform.python_implementation() 33 | python_ver = platform.python_version() 34 | architecture = ' '.join(platform.architecture()) 35 | 36 | print "Operating System: %s, Python " \ 37 | "Version: %s %s, Architecture: %s" \ 38 | "" % (opsys, python_imp, python_ver, architecture) 39 | 40 | bot = Bot() 41 | bot.start_time = time.time() 42 | 43 | print 'Loading plugins...' 44 | 45 | # bootstrap the reloader 46 | eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(), 47 | os.path.join('core', 'reload.py'), 'exec')) 48 | reload(init=True) 49 | 50 | config() 51 | if not hasattr(bot, 'config'): 52 | exit() 53 | 54 | print 'Connecting to IRC...' 55 | 56 | bot.conns = {} 57 | 58 | try: 59 | for name, conf in bot.config['connections'].iteritems(): 60 | print 'Connecting to server: %s' % conf['server'] 61 | if conf.get('ssl'): 62 | bot.conns[name] = SSLIRC(name, conf['server'], conf['nick'], conf=conf, 63 | port=conf.get('port', 6697), channels=conf['channels'], 64 | ignore_certificate_errors=conf.get('ignore_cert', True)) 65 | else: 66 | bot.conns[name] = IRC(name, conf['server'], conf['nick'], conf=conf, 67 | port=conf.get('port', 6667), channels=conf['channels']) 68 | except Exception as e: 69 | print 'ERROR: malformed config file', e 70 | sys.exit() 71 | 72 | bot.persist_dir = os.path.abspath('persist') 73 | if not os.path.exists(bot.persist_dir): 74 | os.mkdir(bot.persist_dir) 75 | 76 | print 'Connection(s) made, starting main loop.' 77 | 78 | while True: 79 | reload() # these functions only do things 80 | config() # if changes have occured 81 | 82 | for conn in bot.conns.itervalues(): 83 | try: 84 | out = conn.out.get_nowait() 85 | main(conn, out) 86 | except Queue.Empty: 87 | pass 88 | while all(conn.out.empty() for conn in bot.conns.itervalues()): 89 | time.sleep(.1) 90 | -------------------------------------------------------------------------------- /config.default: -------------------------------------------------------------------------------- 1 | { 2 | "acls": {}, 3 | "admins": [ 4 | "infinity@*like.lolis", 5 | "infinity@i.like.lolis" 6 | ], 7 | "api_keys": { 8 | "bitly_api": "INSERT API KEY FROM bitly.com HERE", 9 | "bitly_user": "INSERT USERNAME FROM bitly.com HERE", 10 | "ebay": "", 11 | "exhentai": "username:password", 12 | "geoip": "INSERT API KEY FROM ipinfodb.com HERE", 13 | "iex": "INSERT API KEY FROM https://iexcloud.io/ HERE", 14 | "lastfm": "", 15 | "mc_pass": "INSERT minecraft PASSWORD HERE", 16 | "mc_user": "INSERT minecraft USERNAME HERE", 17 | "rottentomatoes": "", 18 | "spotify_client_id": "INSERT CLIENT ID FROM https://developer.spotify.com/dashboard/ HERE", 19 | "spotify_client_secret": "INSERT CLIENT SECRET FROM https://developer.spotify.com/dashboard/ HERE", 20 | "tvdb": "", 21 | "twitter_access_secret": "", 22 | "twitter_access_token": "", 23 | "twitter_consumer_key": "", 24 | "twitter_consumer_secret": "", 25 | "wolframalpha": "", 26 | "yahoo": "", 27 | "youtube":"" 28 | }, 29 | "censored_strings": [ 30 | "censored", 31 | "password" 32 | ], 33 | "connections": { 34 | "Rizon": { 35 | "auto_rejoin": false, 36 | "channels": [ 37 | "#uguubot", 38 | "#devbot" 39 | ], 40 | "command_prefix": ".,+", 41 | "invite_join": false, 42 | "nick": "uguubot2", 43 | "nickserv_password": "", 44 | "realname": "uguu", 45 | "server": "irc.rizon.io", 46 | "user": "uguu" 47 | } 48 | }, 49 | "disabled_commands": [], 50 | "disabled_plugins": [], 51 | "ignored": [ 52 | "ignored nick here" 53 | ], 54 | "owner": "infinity", 55 | "plugins": { 56 | "factoids": { 57 | "prefix": false 58 | }, 59 | "times": { 60 | "format": "%I:%M %p %Z", 61 | "separator": " | ", 62 | "time_zones": [ 63 | ["Los Angeles", "America/Los_Angeles"], 64 | ["New York", "America/New_York"], 65 | ["London", "Europe/London"], 66 | ["Berlin", "Europe/Berlin"], 67 | ["Kiev", "Europe/Kiev"], 68 | ["Tokyo", "Asia/Tokyo"] 69 | ] 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import json 3 | import os 4 | 5 | 6 | def save(conf): 7 | json.dump(conf, open('config', 'w'), sort_keys=True, indent=2) 8 | 9 | if not os.path.exists('config'): 10 | print "Please rename 'config.default' to 'config' to set up your bot!" 11 | print "For help, see https://github.com/infinitylabs/UguuBot" 12 | print "Thank you for using UguuBot!" 13 | sys.exit() 14 | 15 | 16 | def config(): 17 | # reload config from file if file has changed 18 | config_mtime = os.stat('config').st_mtime 19 | if bot._config_mtime != config_mtime: 20 | try: 21 | bot.config = json.load(open('config')) 22 | bot._config_mtime = config_mtime 23 | except ValueError, e: 24 | print 'error: malformed config', e 25 | 26 | 27 | bot._config_mtime = 0 -------------------------------------------------------------------------------- /core/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import thread 4 | 5 | threaddbs = {} 6 | 7 | 8 | def get_db_connection(conn, name=''): 9 | "returns an sqlite3 connection to a persistent database" 10 | 11 | if not name: 12 | name = '{}.db'.format(conn.name) 13 | 14 | threadid = thread.get_ident() 15 | if name in threaddbs and threadid in threaddbs[name]: 16 | return threaddbs[name][threadid] 17 | filename = os.path.join(bot.persist_dir, name) 18 | 19 | db = sqlite3.connect(filename, timeout=1) 20 | if name in threaddbs: 21 | threaddbs[name][threadid] = db 22 | else: 23 | threaddbs[name] = {threadid: db} 24 | return db 25 | 26 | bot.get_db_connection = get_db_connection 27 | -------------------------------------------------------------------------------- /plugins/_broken/answers.py: -------------------------------------------------------------------------------- 1 | from util import hook, web, text 2 | 3 | 4 | @hook.command 5 | def answer(inp): 6 | "answer -- find the answer to a question on Yahoo! Answers" 7 | 8 | query = "SELECT Subject, ChosenAnswer, Link FROM answers.search WHERE query=@query LIMIT 1" 9 | result = web.query(query, {"query": inp.strip()}).one() 10 | 11 | short_url = web.isgd(result["Link"]) 12 | 13 | # we split the answer and .join() it to remove newlines/extra spaces 14 | answer = text.truncate_str(' '.join(result["ChosenAnswer"].split()), 80) 15 | 16 | return u'\x02{}\x02 "{}" - {}'.format(result["Subject"], answer, short_url) -------------------------------------------------------------------------------- /plugins/_broken/chatbot.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import xml.dom.minidom 4 | import re 5 | import socket 6 | from util import hook 7 | 8 | chatbot_re = (r'(^.*\b(taiga|taigabot)\b.*$)', re.I) 9 | @hook.regex(*chatbot_re) 10 | @hook.command 11 | def chatbot(inp, reply=None, nick=None, conn=None): 12 | inp = inp.group(1).lower().replace('taigabot', '').replace('taiga', '').replace(':', '') 13 | args = {'bot_id': '6', 'say': inp.strip(), 'convo_id': conn.nick, 'format': 'xml'} 14 | data = urllib.urlencode(args) 15 | resp = False 16 | url_response = urllib2.urlopen('http://api.program-o.com/v2/chatbot/?', data) 17 | response = url_response.read() 18 | response_dom = xml.dom.minidom.parseString(response) 19 | text = response_dom.getElementsByTagName('response')[0].childNodes[0].data.strip() 20 | return nick + ': ' + str(text.lower().replace('programo', 'taiga').replace('program-o', 'taigabot').replace('elizabeth', 'wednesday')) 21 | -------------------------------------------------------------------------------- /plugins/_broken/mylifeisaverage.py: -------------------------------------------------------------------------------- 1 | from util import hook, http 2 | import random 3 | 4 | mlia_cache = [] 5 | 6 | def refresh_cache(): 7 | "gets a page of random MLIAs and puts them into a dictionary " 8 | url = 'http://mylifeisaverage.com/%s' % random.randint(1,11000) 9 | soup = http.get_soup(url) 10 | 11 | for story in soup.find_all('div', {'class': 'story '}): 12 | mlia_id = story.find('span', {'class': 'left'}).a.text 13 | mlia_text = story.find('div', {'class': 'sc'}).text.strip() 14 | mlia_cache.append((mlia_id, mlia_text)) 15 | 16 | # do an initial refresh of the cache 17 | refresh_cache() 18 | 19 | @hook.command(autohelp=False) 20 | def mlia(inp, reply=None): 21 | "MyLifeIsAverage -- Gets a random quote from MyLifeIsAverage.com." 22 | #grab the last item in the mlia cache and remove it 23 | id, text = mlia_cache.pop() 24 | # reply with the mlia we grabbed 25 | reply('(%s) %s' % (id, text)) 26 | # refresh mlia cache if its getting empty 27 | if len(mlia_cache) < 3: 28 | refresh_cache() 29 | -------------------------------------------------------------------------------- /plugins/_broken/rss.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web, text 2 | 3 | 4 | @hook.command("feed") 5 | @hook.command 6 | def rss(inp, say=None): 7 | "rss -- Gets the first three items from the RSS feed ." 8 | limit = 3 9 | 10 | # preset news feeds 11 | strip = inp.lower().strip() 12 | if strip == "bukkit": 13 | feed = "http://dl.bukkit.org/downloads/craftbukkit/feeds/latest-rb.rss" 14 | limit = 1 15 | elif strip == "xkcd": 16 | feed = "http://xkcd.com/rss.xml" 17 | elif strip == "ars": 18 | feed = "http://feeds.arstechnica.com/arstechnica/index" 19 | else: 20 | feed = inp 21 | 22 | query = "SELECT title, link FROM rss WHERE url=@feed LIMIT @limit" 23 | result = web.query(query, {"feed": feed, "limit": limit}) 24 | 25 | if not result.rows: 26 | return "Could not find/read RSS feed." 27 | 28 | for row in result.rows: 29 | title = text.truncate_str(row["title"], 100) 30 | try: 31 | link = web.isgd(row["link"]) 32 | except (web.ShortenError, http.HTTPError, http.URLError): 33 | link = row["link"] 34 | say(u"{} - {}".format(title, link)) 35 | 36 | 37 | @hook.command(autohelp=False) 38 | def rb(inp, say=None): 39 | "rb -- Shows the latest Craftbukkit recommended build" 40 | rss("bukkit", say) 41 | -------------------------------------------------------------------------------- /plugins/_broken/snopes.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from util import hook, http, formatting 4 | 5 | 6 | search_url = "http://search.atomz.com/search/?sp_a=00062d45-sp00000000" 7 | 8 | 9 | @hook.command 10 | def snopes(inp): 11 | """snopes -- Searches snopes for an urban legend about .""" 12 | 13 | search_page = http.get_html(search_url, sp_q=inp, sp_c="1") 14 | result_urls = search_page.xpath("//a[@target='_self']/@href") 15 | 16 | if not result_urls: 17 | return "no matching pages found" 18 | 19 | snopes_page = http.get_html(result_urls[0]) 20 | snopes_text = snopes_page.text_content() 21 | 22 | claim = re.search(r"Claim: .*", snopes_text).group(0).strip() 23 | status = re.search(r"Status: .*", snopes_text) 24 | 25 | if status is not None: 26 | status = status.group(0).strip() 27 | else: # new-style statuses 28 | status = "Status: %s." % re.search(r"FALSE|TRUE|MIXTURE|UNDETERMINED", 29 | snopes_text).group(0).title() 30 | 31 | claim = re.sub(r"[\s\xa0]+", " ", claim) # compress whitespace 32 | status = re.sub(r"[\s\xa0]+", " ", status) 33 | 34 | return formatting.output('Snopes', ['{} {} {}'.format(claim, status, result_urls[0])]) 35 | -------------------------------------------------------------------------------- /plugins/_broken/stock.py: -------------------------------------------------------------------------------- 1 | from util import hook, web 2 | 3 | 4 | @hook.command 5 | def stock(inp): 6 | """stock -- gets stock information""" 7 | sym = inp.strip().lower() 8 | 9 | query = "SELECT * FROM yahoo.finance.quote WHERE symbol=@symbol LIMIT 1" 10 | quote = web.query(query, {"symbol": sym}).one() 11 | 12 | # if we dont get a company name back, the symbol doesn't match a company 13 | if quote['Change'] is None: 14 | return "Unknown ticker symbol: {}".format(sym) 15 | 16 | change = float(quote['Change']) 17 | price = float(quote['LastTradePriceOnly']) 18 | 19 | if change < 0: 20 | quote['color'] = "5" 21 | else: 22 | quote['color'] = "3" 23 | 24 | quote['PercentChange'] = 100 * change / (price - change) 25 | # print quote 26 | 27 | return u"\x02{Name}\x02 (\x02{symbol}\x02) - {LastTradePriceOnly} " \ 28 | "\x03{color}{Change} ({PercentChange:.2f}%)\x03 " \ 29 | "Day Range: {DaysRange} " \ 30 | "MCAP: {MarketCapitalization}".format(**quote) 31 | -------------------------------------------------------------------------------- /plugins/_disabled/bf.py: -------------------------------------------------------------------------------- 1 | '''brainfuck interpreter adapted from (public domain) code at 2 | http://brainfuck.sourceforge.net/brain.py''' 3 | 4 | import re 5 | import random 6 | 7 | from util import hook 8 | 9 | 10 | BUFFER_SIZE = 5000 11 | MAX_STEPS = 1000000 12 | 13 | 14 | @hook.command('brainfuck') 15 | @hook.command 16 | def bf(inp): 17 | "bf -- Executes as Brainfuck code." 18 | 19 | program = re.sub('[^][<>+-.,]', '', inp) 20 | 21 | # create a dict of brackets pairs, for speed later on 22 | brackets = {} 23 | open_brackets = [] 24 | for pos in range(len(program)): 25 | if program[pos] == '[': 26 | open_brackets.append(pos) 27 | elif program[pos] == ']': 28 | if len(open_brackets) > 0: 29 | brackets[pos] = open_brackets[-1] 30 | brackets[open_brackets[-1]] = pos 31 | open_brackets.pop() 32 | else: 33 | return 'unbalanced brackets' 34 | if len(open_brackets) != 0: 35 | return 'unbalanced brackets' 36 | 37 | # now we can start interpreting 38 | ip = 0 # instruction pointer 39 | mp = 0 # memory pointer 40 | steps = 0 41 | memory = [0] * BUFFER_SIZE # initial memory area 42 | rightmost = 0 43 | output = "" # we'll save the output here 44 | 45 | # the main program loop: 46 | while ip < len(program): 47 | c = program[ip] 48 | if c == '+': 49 | memory[mp] = memory[mp] + 1 % 256 50 | elif c == '-': 51 | memory[mp] = memory[mp] - 1 % 256 52 | elif c == '>': 53 | mp += 1 54 | if mp > rightmost: 55 | rightmost = mp 56 | if mp >= len(memory): 57 | # no restriction on memory growth! 58 | memory.extend([0] * BUFFER_SIZE) 59 | elif c == '<': 60 | mp = mp - 1 % len(memory) 61 | elif c == '.': 62 | output += chr(memory[mp]) 63 | if len(output) > 500: 64 | break 65 | elif c == ',': 66 | memory[mp] = random.randint(1, 255) 67 | elif c == '[': 68 | if memory[mp] == 0: 69 | ip = brackets[ip] 70 | elif c == ']': 71 | if memory[mp] != 0: 72 | ip = brackets[ip] 73 | 74 | ip += 1 75 | steps += 1 76 | if steps > MAX_STEPS: 77 | if output == '': 78 | output = '(no output)' 79 | output += '[exceeded %d iterations]' % MAX_STEPS 80 | break 81 | 82 | stripped_output = re.sub(r'[\x00-\x1F]', '', output) 83 | 84 | if stripped_output == '': 85 | if output != '': 86 | return 'no printable output' 87 | return 'no output' 88 | 89 | return stripped_output[:430].decode('utf8', 'ignore') 90 | -------------------------------------------------------------------------------- /plugins/_disabled/correction.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import re 3 | 4 | 5 | @hook.regex(r'^(?:s|S)/(.+/.*)\S*$') 6 | def correction(inp, nick=None, chan=None, say=None, input=None, notice=None, db=None): 7 | 8 | # if inpsplit[3]: nick = inpsplit[3] 9 | # else: nick = nick 10 | print 'penis' 11 | 12 | last_message = db.execute("select name, quote from seen where name like ? and chan = ?", (nick.lower(), chan.lower())).fetchone() 13 | 14 | if last_message: 15 | message = last_message[1] 16 | inpsplit = inp.group(0).split("/") 17 | find = inpsplit[1] 18 | replace = inpsplit[2] 19 | if find in message: 20 | if "\x01ACTION" in message: message = message.replace("\x01ACTION ", "/me ").replace("\x01", "") 21 | say(u"<{}> {}".format(nick, message.replace(find, "\x02" + replace + "\x02"))) 22 | #else: 23 | # notice(u"{} can't be found in your last message".format(find)) 24 | else: 25 | if nick == input.nick: 26 | notice(u"I haven't seen you say anything here yet") 27 | else: 28 | notice(u"I haven't seen {} say anything here yet".format(nick)) 29 | -------------------------------------------------------------------------------- /plugins/_disabled/domains.py: -------------------------------------------------------------------------------- 1 | from util import hook, http 2 | import urlparse 3 | import re 4 | # import whois 5 | 6 | # @hook.command 7 | # def whois(inp): 8 | # """whois - lookup domains information""" 9 | # w = whois('google.com') 10 | # print w.expiration_date 11 | 12 | 13 | 14 | #for x in domain: 15 | # print "{}: {}".format(x,domain[x]) 16 | #{'expiration_date': datetime.datetime(2020, 9, 14, 0, 0), 'last_updated': datetime.datetime(2011, 7, 20, 0, 0), 'registrar': 'MARKMONITOR INC.', 'name': 'google.com', 'creation_date': datetime.datetime(1997, 9, 15, 0, 0)} 17 | # domain = pythonwhois.get_whois(inp) 18 | # print(domain.id) 19 | # print(domain.status) 20 | # print(domain.creation_date) 21 | # print(domain.expiration_date) 22 | # print(domain.registrar) 23 | # print(domain.nameservers) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | # @hook.command 34 | # def domain(inp): 35 | # url = "http://who.is/whois/{}".format(inp) 36 | # page = http.get_html(url) 37 | # server_type = page.xpath("//span@data-bind-domain='server_type']/text()")[0].strip() 38 | # return server_type 39 | 40 | @hook.command 41 | def domainr(inp): 42 | """domainr - Use domain.nr's API to search for a domain, and similar domains.""" 43 | try: 44 | data = http.get_json('http://domai.nr/api/json/search?q=' + inp) 45 | except (http.URLError, http.HTTPError) as e: 46 | return "Unable to get data for some reason. Try again later." 47 | if data['query'] == "": 48 | return "An error occurrred: {status} - {message}".format(**data['error']) 49 | domains = "" 50 | for domain in data['results']: 51 | domains += ("\x034" if domain['availability'] == "taken" else ( 52 | "\x033" if domain['availability'] == "available" else "\x031")) + domain['domain'] + "\x0f" + domain[ 53 | 'path'] + ", " 54 | return "Domains: " + domains 55 | 56 | 57 | @hook.command('isup') 58 | @hook.command 59 | def isdown(inp): 60 | "isdown -- Checks if the site at is up or down." 61 | 62 | if 'http://' not in inp: 63 | inp = 'http://' + inp 64 | 65 | inp = 'http://' + urlparse.urlparse(inp).netloc 66 | 67 | # http://mail.python.org/pipermail/python-list/2006-December/589854.html 68 | try: 69 | http.get(inp, get_method='HEAD') 70 | return inp + ' seems to be up' 71 | except http.URLError: 72 | return inp + ' seems to be down' 73 | -------------------------------------------------------------------------------- /plugins/_disabled/dotnetpad.py: -------------------------------------------------------------------------------- 1 | "dotnetpad.py: by sklnd, because gobiner wouldn't shut up" 2 | 3 | import urllib 4 | import httplib 5 | import socket 6 | import json 7 | 8 | from util import hook 9 | 10 | 11 | def dotnetpad(lang, code, timeout=30): 12 | "Posts a provided snippet of code in a provided langugage to dotnetpad.net" 13 | 14 | code = code.encode('utf8') 15 | params = urllib.urlencode({'language': lang, 'code': code}) 16 | 17 | headers = {"Content-type": "application/x-www-form-urlencoded", 18 | "Accept": "text/plain"} 19 | 20 | try: 21 | conn = httplib.HTTPConnection("dotnetpad.net", 80, timeout=timeout) 22 | conn.request("POST", "/Skybot", params, headers) 23 | response = conn.getresponse() 24 | except httplib.HTTPException: 25 | conn.close() 26 | return 'error: dotnetpad is broken somehow' 27 | except socket.error: 28 | return 'error: unable to connect to dotnetpad' 29 | 30 | try: 31 | result = json.loads(response.read()) 32 | except ValueError: 33 | conn.close() 34 | return 'error: dotnetpad is broken somehow' 35 | 36 | conn.close() 37 | 38 | if result['Errors']: 39 | return 'First error: %s' % (result['Errors'][0]['ErrorText']) 40 | elif result['Output']: 41 | return result['Output'].lstrip() 42 | else: 43 | return 'No output' 44 | 45 | 46 | @hook.command 47 | def fs(inp): 48 | ".fs -- post a F# code snippet to dotnetpad.net and print the results" 49 | 50 | return dotnetpad('fsharp', inp) 51 | 52 | 53 | @hook.command 54 | def cs(snippet): 55 | ".cs -- post a C# code snippet to dotnetpad.net and print the results" 56 | 57 | file_template = ('using System; ' 58 | 'using System.Linq; ' 59 | 'using System.Collections.Generic; ' 60 | 'using System.Text; ' 61 | '%s') 62 | 63 | class_template = ('public class Default ' 64 | '{' 65 | ' %s \n' 66 | '}') 67 | 68 | main_template = ('public static void Main(String[] args) ' 69 | '{' 70 | ' %s \n' 71 | '}') 72 | 73 | # There are probably better ways to do the following, but I'm feeling lazy 74 | # if no main is found in the snippet, use the template with Main in it 75 | if 'public static void Main' not in snippet: 76 | code = main_template % snippet 77 | code = class_template % code 78 | code = file_template % code 79 | 80 | # if Main is found, check for class and see if we need to use the 81 | # classed template 82 | elif 'class' not in snippet: 83 | code = class_template % snippet 84 | code = file_template % code 85 | 86 | return 'Error using dotnetpad' 87 | # if we found class, then use the barebones template 88 | else: 89 | code = file_template % snippet 90 | 91 | return dotnetpad('csharp', code) 92 | -------------------------------------------------------------------------------- /plugins/_disabled/explain.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | from pycparser.cdecl import explain_c_declaration 3 | 4 | 5 | @hook.command 6 | def explain(inp): 7 | ".explain -- gives an explanation of C expression" 8 | 9 | inp = inp.encode('utf8', 'ignore') 10 | 11 | try: 12 | return explain_c_declaration(inp) 13 | except Exception, e: 14 | return 'error: %s' % e 15 | -------------------------------------------------------------------------------- /plugins/_disabled/fact.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web 2 | 3 | 4 | @hook.command(autohelp=False) 5 | def fact(inp, say=False, nick=False): 6 | "fact -- Gets a random fact from OMGFACTS." 7 | 8 | attempts = 0 9 | 10 | # all of this is because omgfacts is fail 11 | while True: 12 | try: 13 | soup = http.get_soup('http://www.omg-facts.com/random') 14 | except: 15 | if attempts > 2: 16 | return "Could not find a fact!" 17 | else: 18 | attempts += 1 19 | continue 20 | 21 | response = soup.find('a', {'class': 'surprise'}) 22 | link = response['href'] 23 | fact = ''.join(response.find(text=True)) 24 | 25 | if fact: 26 | fact = fact.strip() 27 | break 28 | else: 29 | if attempts > 2: 30 | return "Could not find a fact!" 31 | else: 32 | attempts += 1 33 | continue 34 | 35 | try: 36 | url = web.isgd(link) 37 | except (web.ShortenError, http.HTTPError): 38 | url = link 39 | 40 | return "%s - %s" % (fact, url) 41 | -------------------------------------------------------------------------------- /plugins/_disabled/fishbans.py: -------------------------------------------------------------------------------- 1 | from util import hook, http 2 | from urllib import quote_plus 3 | 4 | api_url = "http://api.fishbans.com/stats/{}/" 5 | 6 | 7 | @hook.command("bans") 8 | @hook.command 9 | def fishbans(inp): 10 | "fishbans -- Gets information on s minecraft bans from fishbans" 11 | user = inp.strip() 12 | 13 | try: 14 | request = http.get_json(api_url.format(quote_plus(user))) 15 | except (http.HTTPError, http.URLError) as e: 16 | return "Could not fetch ban data from the Fishbans API: {}".format(e) 17 | 18 | if request["success"] == False: 19 | return "Could not fetch ban data for {}.".format(user) 20 | 21 | user_url = "http://fishbans.com/u/{}/".format(user) 22 | ban_count = request["stats"]["totalbans"] 23 | 24 | return "The user \x02{}\x02 has \x02{}\x02 ban(s). See detailed info " \ 25 | "at {}".format(user, ban_count, user_url) 26 | 27 | 28 | @hook.command 29 | def bancount(inp): 30 | "bancount -- Gets a count of s minecraft bans from fishbans" 31 | user = inp.strip() 32 | 33 | try: 34 | request = http.get_json(api_url.format(quote_plus(user))) 35 | except (http.HTTPError, http.URLError) as e: 36 | return "Could not fetch ban data from the Fishbans API: {}".format(e) 37 | 38 | if request["success"] == False: 39 | return "Could not fetch ban data for {}.".format(user) 40 | 41 | user_url = "http://fishbans.com/u/{}/".format(user) 42 | services = request["stats"]["service"] 43 | 44 | out = [] 45 | for service, ban_count in services.items(): 46 | if ban_count != 0: 47 | out.append("{}: \x02{}\x02".format(service, ban_count)) 48 | else: 49 | pass 50 | 51 | if not out: 52 | return "The user \x02{}\x02 has no bans.".format(user) 53 | else: 54 | # dat string. 55 | return "Bans for \x02{}\x02: ".format(user) + ", ".join(out) + ". More info " \ 56 | "at {}".format(user_url) 57 | -------------------------------------------------------------------------------- /plugins/_disabled/gainz.py: -------------------------------------------------------------------------------- 1 | from util import hook, database, http 2 | import random 3 | 4 | # RATINGS 5 | # .RATE INFINITY BATTLESTATION 8/10 6 | 7 | # .BS WOULD DISPLAY RATING AND TOTAL VOTES 8 | #TYPE, NICK, VOTES, VOTERS 9 | 10 | ### Battlestations 11 | @hook.command(autohelp=False) 12 | def stats(inp, nick=None, conn=None, chan=None,db=None, notice=None): 13 | "battlestation -- Shows a users Battlestation." 14 | if inp: 15 | if "http" in inp: 16 | database.set(db,'users','battlestation',inp.strip(),'nick',nick) 17 | notice("Saved your battlestation.") 18 | return 19 | elif 'del' in inp: 20 | database.set(db,'users','battlestation','','nick',nick) 21 | notice("Deleted your battlestation.") 22 | return 23 | else: 24 | if '@' in inp: nick = inp.split('@')[1].strip() 25 | else: nick = inp.strip() 26 | 27 | result = database.get(db,'users','battlestation','nick',nick) 28 | if result: 29 | return '{}: {}'.format(nick,result) 30 | else: 31 | if not '@' in inp: notice(battlestation.__doc__) 32 | return 'No battlestation saved for {}.'.format(nick) 33 | 34 | 35 | # 4 main compounds - bench, ohp, deadlift and squat. Body weight, height and bf? -------------------------------------------------------------------------------- /plugins/_disabled/gitio.py: -------------------------------------------------------------------------------- 1 | # Plugin by neersighted and Lukeroge 2 | from util import hook 3 | import urllib2 4 | 5 | 6 | @hook.command 7 | def gitio(inp): 8 | "gitio [code] -- Shorten Github URLs with git.io. [code] is" \ 9 | " a optional custom short code." 10 | split = inp.split(" ") 11 | url = split[0] 12 | 13 | try: 14 | code = split[1] 15 | except: 16 | code = None 17 | 18 | # if the first 8 chars of "url" are not "https://" then append 19 | # "https://" to the url, also convert "http://" to "https://" 20 | if url[:8] != "https://": 21 | if url[:7] != "http://": 22 | url = "https://" + url 23 | else: 24 | url = "https://" + url[7:] 25 | url = 'url=' + str(url) 26 | if code: 27 | url = url + '&code=' + str(code) 28 | req = urllib2.Request(url='http://git.io', data=url) 29 | 30 | # try getting url, catch http error 31 | try: 32 | f = urllib2.urlopen(req) 33 | except urllib2.HTTPError: 34 | return "Failed to get URL!" 35 | urlinfo = str(f.info()) 36 | 37 | # loop over the rows in urlinfo and pick out location and 38 | # status (this is pretty odd code, but urllib2.Request is weird) 39 | for row in urlinfo.split("\n"): 40 | if row.find("Status") != -1: 41 | status = row 42 | if row.find("Location") != -1: 43 | location = row 44 | 45 | print status 46 | if not "201" in status: 47 | return "Failed to get URL!" 48 | 49 | # this wont work for some reason, so lets ignore it ^ 50 | 51 | # return location, minus the first 10 chars 52 | return location[10:] 53 | -------------------------------------------------------------------------------- /plugins/_disabled/isdown.py: -------------------------------------------------------------------------------- 1 | import urlparse 2 | import re 3 | from util import hook, http 4 | 5 | @hook.command('isup') 6 | @hook.command 7 | def isdown(inp): 8 | "isdown -- Checks if the site at is up or down." 9 | 10 | if 'http://' not in inp: 11 | inp = 'http://' + inp 12 | 13 | inp = 'http://' + urlparse.urlparse(inp).netloc 14 | 15 | # http://mail.python.org/pipermail/python-list/2006-December/589854.html 16 | try: 17 | http.get(inp, get_method='HEAD') 18 | return inp + ' seems to be up' 19 | except http.URLError: 20 | return inp + ' seems to be down' 21 | -------------------------------------------------------------------------------- /plugins/_disabled/mcitems.py: -------------------------------------------------------------------------------- 1 | """ plugin by _303 (?) 2 | """ 3 | 4 | from util import hook 5 | import re 6 | import itertools 7 | 8 | pattern = re.compile(r'^(?P\d+)x (?P.+?): (?P.*)$') 9 | 10 | recipelist = [] 11 | 12 | 13 | class Recipe(object): 14 | __slots__ = 'output', 'count', 'ingredients', 'line' 15 | 16 | def __init__(self, output, count, ingredients, line): 17 | self.output = output 18 | self.count = count 19 | self.ingredients = ingredients 20 | self.line = line 21 | 22 | def __str__(self): 23 | return self.line 24 | 25 | 26 | with open("plugins/data/recipes.txt") as f: 27 | for line in f.readlines(): 28 | if line.startswith("//"): 29 | continue 30 | line = line.strip() 31 | match = pattern.match(line) 32 | if not match: 33 | continue 34 | recipelist.append(Recipe(line=line, 35 | output=match.group("name").lower(), 36 | ingredients=match.group("ingredients"), 37 | count=match.group("count"))) 38 | 39 | ids = [] 40 | 41 | with open("plugins/data/itemids.txt") as f: 42 | for line in f.readlines(): 43 | if line.startswith("//"): 44 | continue 45 | parts = line.strip().split() 46 | id = parts[0] 47 | name = " ".join(parts[1:]) 48 | ids.append((id, name)) 49 | 50 | 51 | @hook.command("mcid") 52 | @hook.command 53 | def mcitem(input, reply=None): 54 | "mcitem -- gets the id from an item or vice versa" 55 | input = input.lower().strip() 56 | 57 | if input == "": 58 | reply("error: no input.") 59 | return 60 | 61 | results = [] 62 | 63 | for id, name in ids: 64 | if input == id: 65 | results = ["\x02[%s]\x02 %s" % (id, name)] 66 | break 67 | elif input in name.lower(): 68 | results.append("\x02[%s]\x02 %s" % (id, name)) 69 | 70 | if not results: 71 | return "No matches found." 72 | 73 | if len(results) > 12: 74 | reply("There are too many options, please narrow your search. " \ 75 | "(%s)" % len(results)) 76 | return 77 | 78 | out = ", ".join(results) 79 | 80 | return out 81 | 82 | 83 | @hook.command("mccraft") 84 | @hook.command 85 | def mcrecipe(input, reply=None): 86 | "mcrecipe -- gets the crafting recipe for an item" 87 | input = input.lower().strip() 88 | 89 | results = [recipe.line for recipe in recipelist 90 | if input in recipe.output] 91 | 92 | if not results: 93 | return "No matches found." 94 | 95 | if len(results) > 3: 96 | reply("There are too many options, please narrow your search. " \ 97 | "(%s)" % len(results)) 98 | return 99 | 100 | for result in results: 101 | reply(result) 102 | -------------------------------------------------------------------------------- /plugins/_disabled/mcwiki.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, text 2 | import re 3 | 4 | api_url = "http://minecraftwiki.net/api.php?action=opensearch" 5 | mc_url = "http://minecraftwiki.net/wiki/" 6 | 7 | 8 | @hook.command 9 | def mcwiki(inp): 10 | "mcwiki -- Gets the first paragraph of" \ 11 | " the Minecraft Wiki article on ." 12 | 13 | j = http.get_json(api_url, search=inp) 14 | 15 | if not j[1]: 16 | return "No results found." 17 | article_name = j[1][0].replace(' ', '_').encode('utf8') 18 | 19 | url = mc_url + http.quote(article_name, '') 20 | page = http.get_html(url) 21 | 22 | for p in page.xpath('//div[@class="mw-content-ltr"]/p'): 23 | if p.text_content(): 24 | summary = " ".join(p.text_content().splitlines()) 25 | summary = re.sub("\[\d+\]", "", summary) 26 | summary = text.truncate_str(summary, 250) 27 | return "%s :: \x02%s\x02" % (summary, url) 28 | 29 | return "Unknown Error." 30 | -------------------------------------------------------------------------------- /plugins/_disabled/munge.py: -------------------------------------------------------------------------------- 1 | from util import hook, text 2 | 3 | 4 | @hook.command 5 | def munge(inp): 6 | "munge -- Munges up ." 7 | return text.munge(inp) 8 | -------------------------------------------------------------------------------- /plugins/_disabled/oblique.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from util import hook, http 4 | 5 | 6 | commands_modtime = 0 7 | commands = {} 8 | 9 | 10 | def update_commands(force=False): 11 | global commands_modtime, commands 12 | 13 | if force or time.time() - commands_modtime > 60 * 60: # update hourly 14 | h = http.get_html('http://wiki.github.com/nslater/oblique/') 15 | 16 | lines = h.xpath('//li/text()') 17 | commands = {} 18 | for line in lines: 19 | if not line.strip(): 20 | continue 21 | 22 | if line.strip().find(" ") == -1: 23 | continue 24 | 25 | name, url = line.strip().split(None, 1) 26 | commands[name] = url 27 | 28 | commands_modtime = time.time() 29 | 30 | 31 | @hook.command('o') 32 | @hook.command 33 | def oblique(inp, nick='', chan=''): 34 | '.o/.oblique -- runs using oblique web' 35 | ' services. see http://wiki.github.com/nslater/oblique/' 36 | 37 | update_commands() 38 | 39 | if ' ' in inp: 40 | command, args = inp.split(None, 1) 41 | else: 42 | command = inp 43 | args = '' 44 | 45 | command = command.lower() 46 | 47 | if command == 'refresh': 48 | update_commands(True) 49 | return '%d commands loaded.' % len(commands) 50 | if command in commands: 51 | url = commands[command] 52 | url = url.replace('${nick}', nick) 53 | url = url.replace('${sender}', chan) 54 | url = url.replace('${args}', http.quote(args.encode('utf8'))) 55 | try: 56 | return http.get(url) 57 | except http.HTTPError, e: 58 | return "http error %d" % e.code 59 | else: 60 | return 'no such service' 61 | -------------------------------------------------------------------------------- /plugins/_disabled/perl.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import subprocess 3 | 4 | @hook.command(adminonly=True) 5 | def perl(inp): 6 | ".perl -e " 7 | return subprocess.Popen(["perl", inp], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate() 8 | -------------------------------------------------------------------------------- /plugins/_disabled/ping.py: -------------------------------------------------------------------------------- 1 | # ping plugin by neersighted 2 | from util import hook 3 | import time 4 | import subprocess 5 | import re 6 | import os 7 | 8 | 9 | 10 | @hook.command('pingme', autohelp=False) 11 | @hook.command(autohelp=False) 12 | def ping(inp, nick=None, chan=None, conn=None, notice=None, reply=None): 13 | "version -- Returns version " 14 | import core_ctcp 15 | if '.' in inp: 16 | return pingip(inp,reply) 17 | else: 18 | inp = inp.split(" ") 19 | user = inp[0] 20 | if not user: user=nick 21 | #curtime = int((datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds()) 22 | # print len(ctcpcache) 23 | curtime = time.time() 24 | core_ctcp.ctcpcache.append(("PING",user, chan)) 25 | # ctcpcache_timer 26 | conn.send(u"PRIVMSG {} :\x01PING {}\x01".format(user, str(curtime))) 27 | return 28 | 29 | 30 | ping_regex = re.compile(r"(\d+.\d+)/(\d+.\d+)/(\d+.\d+)/(\d+.\d+)") 31 | 32 | @hook.command(adminonly=True) 33 | def pingip(inp, reply=None): 34 | "ping [count] -- Pings [count] times." 35 | 36 | if os.name == "nt": 37 | return "Sorry, this command is not supported on Windows systems." 38 | 39 | args = inp.split(' ') 40 | host = args[0] 41 | 42 | # check for a seccond argument and set the ping count 43 | if len(args) > 1: 44 | count = int(args[1]) 45 | if count > 20: 46 | count = 20 47 | else: 48 | count = 5 49 | 50 | count = str(count) 51 | 52 | host = re.sub(r'([^\s\w\.])+', '', host) 53 | 54 | reply("Attempting to ping %s %s times..." % (host, count)) 55 | 56 | pingcmd = subprocess.check_output(["ping", "-c", count, host]) 57 | if "request timed out" in pingcmd or "unknown host" in pingcmd: 58 | return "error: could not ping host" 59 | else: 60 | m = re.search(ping_regex, pingcmd) 61 | return "min: %sms, max: %sms, average: %sms, range: %sms, count: %s" \ 62 | % (m.group(1), m.group(3), m.group(2), m.group(4), count) 63 | -------------------------------------------------------------------------------- /plugins/_disabled/pomf.py: -------------------------------------------------------------------------------- 1 | ######################################################################################### 2 | # Name pomf 3 | # Description Uploads files and videos to pomf.se 4 | # Version 1.0.1 (2015-04-12) 5 | # Contact ScottSteiner@irc.rizon.net 6 | # Website https://github.com/ScottSteiner/uguubot 7 | # Copyright 2014-2015, ScottSteiner 8 | # License GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html 9 | ######################################################################################### 10 | 11 | import requests, subprocess, tempfile, os, re 12 | import hashtags, datafiles 13 | from mimetypes import guess_extension, guess_type 14 | from util import hook, http, formatting 15 | 16 | @hook.command("pomftube", adminonly=True) 17 | @hook.command(adminonly=True) 18 | def pomf(url): 19 | "pomf -- Downloads file and uploads it" 20 | 21 | return formatting.output('pomf', [upload(url)]) 22 | 23 | @hook.command("pr", adminonly=True) 24 | @hook.command("premember", adminonly=True) 25 | @hook.command(adminonly=True) 26 | def pomfremember(inp, chan=None, nick=None, say=None, db=None, adminonly=True): 27 | "pomfremember -- Downloads file, uploads it and adds it to the dictionary" 28 | 29 | word, url = inp.split(None, 1) 30 | pomfurl = upload(url) 31 | strsave = "{} {}".format(word, pomfurl) 32 | hashtags.remember(strsave, nick, db) 33 | return(formatting.output('pomf', ['{} remembered as {}'.format(word, pomfurl)])) 34 | 35 | @hook.command("padd", adminonly=True) 36 | @hook.command(adminonly=True) 37 | def pomfadd(inp, chan=None, nick=None, notice=None, db=None, say=None): 38 | "pomfadd -- Downloads file, uploads it and adds it to the dictionary" 39 | 40 | dfile, url = inp.split(None, 1) 41 | pomfurl = upload(url) 42 | strsave = "{} {}".format(dfile, pomfurl) 43 | datafiles.add(strsave, notice) 44 | return(formatting.output('pomf', ['{} remembered as {}'.format(pomfurl, dfile)])) 45 | 46 | def upload(url): 47 | cclive = subprocess.Popen("cclive --support | xargs | tr ' ' '|'", stdout=subprocess.PIPE, shell=True) 48 | (cclive_formats, err) = cclive.communicate() 49 | 50 | re_youtube = "youtube|youtu\.be|yooouuutuuube" 51 | search = ".*(?:{}|{}).*".format(re_youtube, cclive_formats) 52 | try: 53 | if re.match(search, url, re.I): 54 | if re.match(".*(?:{}).*".format(re_youtube), url, re.I): 55 | cmd = "youtube-dl --quiet --recode-video webm --format webm/mp4 --output /tmp/%\(id\)s.webm {}".format(url) 56 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 57 | yt = ".*(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)([-_a-zA-Z0-9]+).*" 58 | file = "/tmp/{}.webm".format(re.match(yt, url, re.I).group(1)) 59 | else: 60 | cmd = "cclive --quiet -f fmt43_360p {} --O /tmp/pomf.webm --exec 'echo -n %f'".format(url, "/tmp") 61 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 62 | (file, err) = p.communicate() 63 | else: 64 | headers = { 65 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0', 66 | 'Referer': 'http://www.amazon.com/' 67 | } 68 | 69 | extension = guess_extension(guess_type(url)[0]).replace('jpe','jpg') 70 | temp = tempfile.NamedTemporaryFile(suffix=extension) 71 | content = requests.get(url).content 72 | temp.write(content) 73 | file = temp.name 74 | 75 | fh = open(file, "rb") 76 | fh.seek(0) 77 | 78 | content = requests.post(url="http://pomf.se/upload.php", files={"files[]":fh}) 79 | if not content.status_code // 100 == 2: 80 | raise Exception("Unexpected response {}".format(content)) 81 | return "http://a.pomf.se/{}".format(content.json()["files"][0]["url"]) 82 | except Exception as e: 83 | return "Error: {}".format(e) 84 | 85 | -------------------------------------------------------------------------------- /plugins/_disabled/pre.py: -------------------------------------------------------------------------------- 1 | # searches scene releases using orlydb 2 | 3 | from util import hook, http 4 | 5 | 6 | @hook.command 7 | def predb(inp): 8 | '.predb -- searches scene releases using orlydb.com' 9 | 10 | try: 11 | h = http.get_html("http://orlydb.com/", q=inp) 12 | except HTTPError: 13 | return 'orlydb seems to be down' 14 | 15 | results = h.xpath("//div[@id='releases']/div/span[@class='release']/..") 16 | 17 | if not results: 18 | return "zero results" 19 | 20 | result = results[0] 21 | 22 | date, time = result.xpath("span[@class='timestamp']/text()")[0].split() 23 | section, = result.xpath("span[@class='section']//text()") 24 | name, = result.xpath("span[@class='release']/text()") 25 | 26 | size = result.xpath("span[@class='inforight']//text()") 27 | if size: 28 | size = ' :: ' + size[0].split()[0] 29 | else: 30 | size = '' 31 | 32 | return '%s - %s - %s%s' % (date, section, name, size) 33 | -------------------------------------------------------------------------------- /plugins/_disabled/pyexec.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from util import hook, http 4 | from util.execute import eval_py 5 | 6 | @hook.command(adminonly=True) 7 | def python(inp): 8 | "python -- Executes as Python code." 9 | 10 | return eval_py(inp) 11 | -------------------------------------------------------------------------------- /plugins/_disabled/request.py: -------------------------------------------------------------------------------- 1 | import time 2 | import re 3 | from util import hook, timesince 4 | db_ready=False 5 | 6 | def db_init(db): 7 | "check to see that our db has the request table and return a dbection." 8 | global db_ready 9 | db.execute("create table if not exists requests" 10 | "(user_from, message, chan, time," 11 | "primary key(message))") 12 | db.commit() 13 | db_ready=True 14 | print "Request Database Ready" 15 | 16 | return db 17 | 18 | 19 | def get_requests(db): 20 | return db.execute("select user_from, message, time, chan from requests" 21 | " order by time").fetchall() 22 | 23 | 24 | @hook.singlethread 25 | @hook.event('PRIVMSG') 26 | def requestinput(paraml, input=None, notice=None, db=None, bot=None, nick=None, conn=None): 27 | if 'showrequests' in input.msg.lower(): 28 | return 29 | 30 | if not db_ready: db_init(db) 31 | 32 | requests = get_requests(db) 33 | 34 | if requests: 35 | user_from, message, time, chan = requests[0] 36 | reltime = timesince.timesince(time) 37 | 38 | reply = "{} sent a request {} ago from {}: {}".format(user_from, reltime, chan, message) 39 | if len(requests) > 1: 40 | reply += " (+%d more, %sshowtells to view)" % (len(requests) - 1, conn.conf["command_prefix"]) 41 | 42 | if 'del' in inp: 43 | db.execute("delete from requests where user_from=? and message=?", (user_from, message)) 44 | db.commit() 45 | notice(reply) 46 | 47 | 48 | @hook.command(adminonly=True, autohelp=False) 49 | def showrequests(inp, nick='', chan='', notice=None, db=None, bot=None): 50 | "showrequests [del nick/all] -- View all pending requests (sent in a notice)." 51 | gadmins = bot.config['admins'] 52 | admins = [] 53 | 54 | for admin in gadmins: 55 | admin = admin.split('@') 56 | admins.append(admin[0]) 57 | admins = " ".join(admins) 58 | 59 | if nick not in admins: 60 | return 61 | else: 62 | if not db_ready: db_init(db) 63 | 64 | requests = get_requests(db) 65 | print requests 66 | 67 | if not requests: 68 | notice("You have no pending tells.") 69 | return 70 | 71 | for request in requests: 72 | user_from, message, time, chan = request 73 | past = timesince.timesince(time) 74 | notice("%s sent you a message %s ago from %s: %s" % (user_from, past, chan, message)) 75 | 76 | if 'del' in inp: 77 | inp = inp.split(" ") 78 | if inp[1] == 'all': 79 | db.execute("delete from requests where user_from=?", 80 | (user_from,)) 81 | db.commit() 82 | else: 83 | db.execute("delete from requests where user_from=?", 84 | (inp[1],)) 85 | db.commit() 86 | 87 | @hook.command 88 | def request(inp, nick='', chan='', db=None, input=None, notice=None): 89 | "request -- Relay to gadmins." 90 | query = inp.split(' ', 1) 91 | 92 | if len(query) != 1: 93 | notice(request.__doc__) 94 | return 95 | 96 | message = query[0].strip() 97 | user_from = nick 98 | 99 | if chan.lower() == user_from.lower(): 100 | chan = 'a pm' 101 | 102 | if not db_ready: db_init(db) 103 | 104 | try: 105 | db.execute("insert into requests(user_from, message, chan, time) values(?,?,?,?)", 106 | (user_from, message, chan, time.time())) 107 | db.commit() 108 | except db.IntegrityError: 109 | notice("Request has already been queued.") 110 | return 111 | 112 | notice("Your request will be sent!") 113 | -------------------------------------------------------------------------------- /plugins/_disabled/rottentomatoes.py: -------------------------------------------------------------------------------- 1 | from util import http, hook 2 | 3 | api_root = 'http://api.rottentomatoes.com/api/public/v1.0/' 4 | movie_search_url = api_root + 'movies.json' 5 | movie_reviews_url = api_root + 'movies/%s/reviews.json' 6 | 7 | 8 | @hook.command('rt') 9 | def rottentomatoes(inp, bot=None): 10 | 'rt -- gets ratings for <title> from Rotten Tomatoes' 11 | 12 | api_key = bot.config.get("api_keys", {}).get("rottentomatoes", None) 13 | if not api_key: 14 | return "error: no api key set" 15 | 16 | title = inp.strip() 17 | 18 | results = http.get_json(movie_search_url, q=title, apikey=api_key) 19 | if results['total'] == 0: 20 | return 'No results.' 21 | 22 | movie = results['movies'][0] 23 | title = movie['title'] 24 | id = movie['id'] 25 | critics_score = movie['ratings']['critics_score'] 26 | audience_score = movie['ratings']['audience_score'] 27 | url = movie['links']['alternate'] 28 | 29 | if critics_score == -1: 30 | return 31 | 32 | reviews = http.get_json(movie_reviews_url % id, apikey=api_key, review_type='all') 33 | review_count = reviews['total'] 34 | 35 | fresh = critics_score * review_count / 100 36 | rotten = review_count - fresh 37 | 38 | return u"{} - Critics Rating: \x02{}%\x02 ({} liked, {} disliked) " \ 39 | "Audience Rating: \x02{}%\x02 - {}".format(title, critics_score, fresh, rotten, audience_score, url) -------------------------------------------------------------------------------- /plugins/_disabled/somethingawful.py: -------------------------------------------------------------------------------- 1 | # for crusty old rotor 2 | 3 | from util import hook 4 | 5 | 6 | @hook.command 7 | def profile(inp): 8 | ".profile <username> -- links to <username>'s profile on SA" 9 | 10 | return 'http://forums.somethingawful.com/member.php?action=getinfo' + \ 11 | '&username=' + '+'.join(inp.split()) 12 | -------------------------------------------------------------------------------- /plugins/_disabled/steam.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web, text 2 | import re 3 | 4 | # this is still beta code. some things will be improved later. 5 | 6 | count_re = re.compile(r"Found (.*?) Games with a value of ") 7 | value_re = re.compile(r'\$(\d+\.\d{2})') 8 | 9 | 10 | def db_init(db): 11 | "check to see that our db has the the top steam users table." 12 | db.execute("create table if not exists steam_rankings(id, value, count, " 13 | "primary key(id))") 14 | db.commit() 15 | 16 | 17 | @hook.command 18 | def steamcalc(inp, db=None): 19 | "steamcalc <user> -- Check the value of <user>s steam account." 20 | db_init(db) 21 | 22 | if " " in inp: 23 | return "Invalid Steam ID" 24 | 25 | uid = inp.strip().lower() 26 | url = "http://steamcalculator.com/id/{}".format(http.quote_plus(uid)) 27 | 28 | # get the web page 29 | try: 30 | page = http.get_html(url) 31 | except Exception as e: 32 | return "Could not get Steam game listing: {}".format(e) 33 | 34 | # extract the info we need 35 | try: 36 | count_text = page.xpath("//div[@id='rightdetail']/text()")[0] 37 | count = int(count_re.findall(count_text)[0]) 38 | 39 | value_text = page.xpath("//div[@id='rightdetail']/h1/text()")[0] 40 | value = float(value_re.findall(value_text)[0]) 41 | except IndexError: 42 | return "Could not get Steam game listing." 43 | 44 | # save the info in the DB for steam rankings 45 | db.execute("insert or replace into steam_rankings(id, value, count)" 46 | "values(?,?,?)", (uid, value, count)) 47 | db.commit() 48 | 49 | # shorten the URL 50 | try: 51 | short_url = web.isgd(url) 52 | except web.ShortenError: 53 | short_url = url 54 | 55 | return u"\x02Games:\x02 {}, \x02Total Value:\x02 ${:.2f} USD - {}".format(count, value, short_url) 56 | 57 | 58 | @hook.command(autohelp=False) 59 | def steamtop(inp, db=None): 60 | "steamtop -- Shows the top five users from steamcalc." 61 | rows = [] 62 | for row in db.execute("SELECT id, value, count FROM steam_rankings ORDER BY value DESC LIMIT 5"): 63 | rows.append(u"{} - \x02${:.2f}\x02 ({} games)".format(text.munge(row[0], 1), row[1], row[2])) 64 | 65 | return u"Top Steam Users: {}".format(", ".join(rows)) 66 | -------------------------------------------------------------------------------- /plugins/_disabled/suggest.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import re 4 | 5 | from util import hook, http 6 | 7 | 8 | @hook.command 9 | def suggest(inp, inp_unstripped=''): 10 | ".suggest [#n] <phrase> -- gets a random/the nth suggested google search" 11 | 12 | inp = inp_unstripped 13 | m = re.match('^#(\d+) (.+)$', inp) 14 | if m: 15 | num, inp = m.groups() 16 | num = int(num) 17 | if num > 10: 18 | return 'I can only get the first ten suggestions.' 19 | else: 20 | num = 0 21 | 22 | page = http.get('http://google.com/complete/search', output='json', client='hp', q=inp) 23 | page_json = page.split('(', 1)[1][:-1] 24 | suggestions = json.loads(page_json)[1] 25 | if not suggestions: 26 | return 'No suggestions found.' 27 | if num: 28 | if len(suggestions) + 1 <= num: 29 | return 'I only got %d suggestions.' % len(suggestions) 30 | out = suggestions[num - 1] 31 | else: 32 | out = random.choice(suggestions) 33 | return '#%d: %s' % (int(out[2][0]) + 1, out[0].replace('<b>', '').replace('</b>', '')) -------------------------------------------------------------------------------- /plugins/_disabled/title.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, urlnorm 2 | from bs4 import BeautifulSoup 3 | 4 | 5 | @hook.command 6 | def title(inp): 7 | "title <url> -- gets the title of a web page" 8 | url = urlnorm.normalize(inp.encode('utf-8'), assume_scheme="http") 9 | 10 | try: 11 | page = http.open(url) 12 | real_url = page.geturl() 13 | soup = BeautifulSoup(page.read()) 14 | except (http.HTTPError, http.URLError): 15 | return "Could not fetch page." 16 | 17 | title = soup.find('title').contents[0] 18 | 19 | if not title: 20 | return "Could not find title." 21 | 22 | return u"{} [{}]".format(title, real_url) 23 | -------------------------------------------------------------------------------- /plugins/_disabled/touhouradio.py: -------------------------------------------------------------------------------- 1 | from util import hook,http 2 | 3 | time = 3600 4 | touhou_list = {} 5 | 6 | def gather_subsection(section, key): 7 | if section.depth > 1: 8 | print "Subsection " + section.name 9 | 10 | 11 | @hook.command(channeladminonly=True) 12 | def check_touhou(inp,chan=None,bot=None): 13 | #if channel[chan]: 14 | 15 | channels = bot.channelconfig.walk(gather_subsection) 16 | for channel in channels: 17 | print channel 18 | 19 | return 20 | 21 | chan_url = http.quote('{channel|%s}/1' % '#pantsumen') #str(chan) 22 | url='http://booru.touhouradio.com/post/list/%s' % chan_url 23 | 24 | try: html = http.get_html(url) 25 | except ValueError: return None 26 | 27 | firstimage = html.xpath("//span[@class='thumb']//img/@src")[0] 28 | 29 | try: 30 | if firstimage in touhou_list[chan]: 31 | return "New Activity on TouhouRadio!" 32 | except: 33 | pass 34 | 35 | touhou_list[chan] = firstimage 36 | print touhou_list[chan] 37 | 38 | 39 | @hook.command('touhou',channeladminonly=True) 40 | def touhoucheck(inp, conn=None, chan=None, notice=None, bot=None): 41 | "touhou [channel] <enable|disable> -- Check touhouradio.com for new updates." 42 | inp = inp.lower() 43 | if inp[0][0] == "#": 44 | chan = inp.split()[0] 45 | inp = inp.replace(chan,'').strip() 46 | channel = chan.lower() 47 | 48 | if 'enable' in inp or 'on' in inp: setting=True 49 | elif 'disable' in inp or 'off' in inp: setting=False 50 | else: return __help__ 51 | 52 | try: bot.channelconfig[channel] 53 | except: bot.channelconfig[channel] = {} 54 | 55 | bot.channelconfig[channel]['touhou_check'] = setting 56 | if setting: notice("Touhou checking is now enabled.") 57 | else: notice("Touhou checking is now disabled.") 58 | 59 | bot.channelconfig.write() 60 | return -------------------------------------------------------------------------------- /plugins/_disabled/urlhistory.py: -------------------------------------------------------------------------------- 1 | import math 2 | import re 3 | import time 4 | 5 | from util import hook, urlnorm, timesince 6 | 7 | 8 | expiration_period = 60 * 60 * 24 # 1 day 9 | 10 | ignored_urls = [urlnorm.normalize("http://google.com"),] 11 | 12 | 13 | def db_init(db): 14 | db.execute("create table if not exists urls" 15 | "(chan, url, nick, time)") 16 | db.commit() 17 | 18 | 19 | def insert_history(db, chan, url, nick): 20 | now = time.time() 21 | db.execute("insert into urls(chan, url, nick, time) " 22 | "values(?,?,?,?)", (chan, url, nick, time.time())) 23 | db.commit() 24 | 25 | 26 | def get_history(db, chan, url): 27 | db.execute("delete from urls where time < ?", 28 | (time.time() - expiration_period,)) 29 | return db.execute("select nick, time from urls where " 30 | "chan=? and url=? order by time desc", (chan, url)).fetchall() 31 | 32 | 33 | def nicklist(nicks): 34 | nicks = sorted(dict(nicks), key=unicode.lower) 35 | if len(nicks) <= 2: 36 | return ' and '.join(nicks) 37 | else: 38 | return ', and '.join((', '.join(nicks[:-1]), nicks[-1])) 39 | 40 | 41 | def format_reply(history): 42 | if not history: 43 | return 44 | 45 | last_nick, recent_time = history[0] 46 | last_time = timesince.timesince(recent_time) 47 | 48 | if len(history) == 1: 49 | return #"%s linked that %s ago." % (last_nick, last_time) 50 | 51 | hour_span = math.ceil((time.time() - history[-1][1]) / 3600) 52 | hour_span = '%.0f hours' % hour_span if hour_span > 1 else 'hour' 53 | 54 | hlen = len(history) 55 | ordinal = ["once", "twice", "%d times" % hlen][min(hlen, 3) - 1] 56 | 57 | if len(dict(history)) == 1: 58 | last = "last linked %s ago" % last_time 59 | else: 60 | last = "last linked by %s %s ago" % (last_nick, last_time) 61 | 62 | return #"that url has been posted %s in the past %s by %s (%s)." % (ordinal, 63 | 64 | 65 | link_re = (r'((https?://([-\w\.]+)+(:\d+)?(/([\S/_\.]*(\?\S+)?)?)?))', re.I) 66 | @hook.regex(*link_re) 67 | @hook.command 68 | def url(inp, nick='', chan='', db=None, bot=None): 69 | db_init(db) 70 | url = urlnorm.normalize(inp.group(0).lower().encode('utf-8')) 71 | print url 72 | if url not in ignored_urls: 73 | url = url.decode('utf-8') 74 | history = get_history(db, chan, url) 75 | print history 76 | #insert_history(db, chan, url, nick) 77 | 78 | # inp = url.lower() 79 | 80 | for name in dict(history): 81 | if name.lower() in url: # person was probably quoting a line 82 | return # that had a link. don't remind them. 83 | 84 | if nick not in dict(history): 85 | return format_reply(history) 86 | -------------------------------------------------------------------------------- /plugins/_disabled/valvesounds.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web 2 | import json 3 | import urllib2 4 | 5 | 6 | def get_sound_info(game, search): 7 | search = search.replace(" ", "+") 8 | try: 9 | data = http.get_json("http://p2sounds.blha303.com.au/search/%s/%s?format=json" % (game, search)) 10 | except urllib2.HTTPError as e: 11 | return "Error: " + json.loads(e.read())["error"] 12 | items = [] 13 | for item in data["items"]: 14 | if "music" in game: 15 | textsplit = item["text"].split('"') 16 | text = "" 17 | for i in xrange(len(textsplit)): 18 | if i % 2 != 0 and i < 6: 19 | if text: 20 | text += " / " + textsplit[i] 21 | else: 22 | text = textsplit[i] 23 | else: 24 | text = item["text"] 25 | items.append("{} - {} {}".format(item["who"], 26 | text if len(text) < 325 else text[:325] + "...", 27 | item["listen"])) 28 | if len(items) == 1: 29 | return items[0] 30 | else: 31 | return "{} (and {} others: {})".format(items[0], len(items) - 1, web.haste("\n".join(items))) 32 | 33 | 34 | @hook.command 35 | def portal2(inp): 36 | """portal2 <quote> - Look up Portal 2 quote. 37 | Example: .portal2 demand to see life's manager""" 38 | return get_sound_info("portal2", inp) 39 | 40 | 41 | @hook.command 42 | def portal2dlc(inp): 43 | """portal2dlc <quote> - Look up Portal 2 DLC quote. 44 | Example: .portal2dlc1 these exhibits are interactive""" 45 | return get_sound_info("portal2dlc1", inp) 46 | 47 | 48 | @hook.command("portal2pti") 49 | @hook.command 50 | def portal2dlc2(inp): 51 | """portal2dlc2 <quote> - Look up Portal 2 Perpetual Testing Inititive quote. 52 | Example: .portal2 Cave here.""" 53 | return get_sound_info("portal2dlc2", inp) 54 | 55 | 56 | @hook.command 57 | def portal2music(inp): 58 | """portal2music <title> - Look up Portal 2 music. 59 | Example: .portal2music turret opera""" 60 | return get_sound_info("portal2music", inp) 61 | 62 | 63 | @hook.command('portal1') 64 | @hook.command 65 | def portal(inp): 66 | """portal <quote> - Look up Portal quote. 67 | Example: .portal The last thing you want to do is hurt me""" 68 | return get_sound_info("portal1", inp) 69 | 70 | 71 | @hook.command('portal1music') 72 | @hook.command 73 | def portalmusic(inp): 74 | """portalmusic <title> - Look up Portal music. 75 | Example: .portalmusic still alive""" 76 | return get_sound_info("portal1music", inp) 77 | 78 | 79 | @hook.command('tf2sound') 80 | @hook.command 81 | def tf2(inp): 82 | """tf2 [who - ]<quote> - Look up TF2 quote. 83 | Example: .tf2 may i borrow your earpiece""" 84 | return get_sound_info("tf2", inp) 85 | 86 | 87 | @hook.command 88 | def tf2music(inp): 89 | """tf2music title - Look up TF2 music lyrics. 90 | Example: .tf2music rocket jump waltz""" 91 | return get_sound_info("tf2music", inp) 92 | -------------------------------------------------------------------------------- /plugins/_disabled/wordoftheday.py: -------------------------------------------------------------------------------- 1 | import re 2 | from util import hook, http, misc 3 | from BeautifulSoup import BeautifulSoup 4 | 5 | 6 | @hook.command(autohelp=False) 7 | def word(inp, say=False, nick=False): 8 | "word -- Gets the word of the day." 9 | page = http.get('http://merriam-webster.com/word-of-the-day') 10 | 11 | soup = BeautifulSoup(page) 12 | 13 | word = soup.find('strong', {'class': 'main_entry_word'}).renderContents() 14 | function = soup.find('p', {'class': 'word_function'}).renderContents() 15 | 16 | #definitions = re.findall(r'<span class="ssens"><strong>:</strong>' 17 | # r' *([^<]+)</span>', content) 18 | 19 | say("(%s) The word of the day is:"\ 20 | " \x02%s\x02 (%s)" % (nick, word, function)) 21 | -------------------------------------------------------------------------------- /plugins/_disabled/yandere.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web 2 | import re 3 | import random 4 | 5 | yandere_cache = [] 6 | 7 | def refresh_cache(): 8 | "gets a page of random yande.re posts and puts them into a dictionary " 9 | url = 'https://yande.re/post?page=%s' % random.randint(1,11000) 10 | soup = http.get_soup(url) 11 | 12 | for result in soup.findAll('li'): 13 | title = result.find('img', {'class': re.compile(r'\bpreview\b')}) #['title'] 14 | img = result.find('a', {'class': re.compile(r'\bdirectlink\b')}) #['href'] 15 | if img and title: 16 | yandere_cache.append((result['id'].replace('p','') ,title['title'].split(' User')[0], img['href'])) 17 | 18 | def get_yandere_tags(inp): 19 | url = 'https://yande.re/post?tags=%s' % inp.replace(' ','_') 20 | soup = http.get_soup(url) 21 | imagelist = soup.find('ul', {'id': 'post-list-posts'}).findAll('li') 22 | image = imagelist[random.randint(0,len(imagelist)-1)] 23 | imageid = image["id"].replace('p','') 24 | title = image.find('img')['title'] 25 | src = image.find('a', {'class': 'directlink'})["href"] 26 | return u"\x034NSFW\x03: \x02({})\x02 {}: {}".format(imageid, title, web.isgd(http.unquote(src))) 27 | 28 | #do an initial refresh of the cache 29 | refresh_cache() 30 | 31 | @hook.command(autohelp=False) 32 | def yandere(inp, reply=None): 33 | "tandere [tags] -- Yande.re -- Gets a random image from Yande.re." 34 | 35 | if inp: return get_yandere_tags(inp) 36 | 37 | id, title, image = yandere_cache.pop() 38 | reply(u'\x034NSFW\x03: \x02(%s)\x02 %s: %s' % (id, title[:75], web.isgd(image))) 39 | if len(yandere_cache) < 3: 40 | refresh_cache() -------------------------------------------------------------------------------- /plugins/_unused/core_db.py: -------------------------------------------------------------------------------- 1 | # from util import hook 2 | 3 | # # Create channelsettings 4 | # db.execute("CREATE TABLE if not exists channelsettings(channel, admins, permissions, aop, bans, disabled, ignored, badwords, flood, cmdflood, trim, primary key(channel))") 5 | 6 | 7 | 8 | 9 | # db.commit -------------------------------------------------------------------------------- /plugins/_unused/exhentai.py: -------------------------------------------------------------------------------- 1 | from util import hook, http 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /plugins/_unused/lmgtfy.py: -------------------------------------------------------------------------------- 1 | from util import hook, web, http 2 | 3 | @hook.command('nym') 4 | @hook.command('littleanon') 5 | @hook.command('gfy') 6 | @hook.command 7 | def lmgtfy(inp, bot=None): 8 | "lmgtfy [phrase] - Posts a google link for the specified phrase" 9 | 10 | link = "http://lmgtfy.com/?q=%s" % http.quote_plus(inp) 11 | 12 | try: 13 | return web.isgd(link) 14 | except (web.ShortenError, http.HTTPError): 15 | return link 16 | -------------------------------------------------------------------------------- /plugins/_unused/rottentomatoes.py: -------------------------------------------------------------------------------- 1 | from util import http, hook 2 | 3 | api_root = 'http://api.rottentomatoes.com/api/public/v1.0/' 4 | movie_search_url = api_root + 'movies.json' 5 | movie_reviews_url = api_root + 'movies/%s/reviews.json' 6 | 7 | 8 | 9 | @hook.command('rt') 10 | @hook.command 11 | def rottentomatoes(inp,bot=None): 12 | '.rt <title> -- gets ratings for <title> from Rotten Tomatoes' 13 | 14 | api_key = bot.config.get("api_keys", {}).get("rottentomatoes", None) 15 | if not api_key: 16 | return "error: no api key set" 17 | 18 | results = http.get_json(movie_search_url, q=inp, apikey=api_key) 19 | if results['total'] == 0: 20 | return 'no results' 21 | 22 | movie = results['movies'][0] 23 | title = movie['title'] 24 | id = movie['id'] 25 | critics_score = movie['ratings']['critics_score'] 26 | audience_score = movie['ratings']['audience_score'] 27 | url = movie['links']['alternate'] 28 | 29 | if critics_score == -1: 30 | return 31 | 32 | reviews = http.get_json(movie_reviews_url % id, apikey=api_key, review_type='all') 33 | review_count = reviews['total'] 34 | 35 | fresh = critics_score * review_count / 100 36 | rotten = review_count - fresh 37 | 38 | return u"%s - critics: \x02%d%%\x02 (%d\u2191/%d\u2193) audience: \x02%d%%\x02 - %s" % (title, critics_score, fresh, rotten, audience_score, url) 39 | -------------------------------------------------------------------------------- /plugins/_unused/shorten.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web 2 | 3 | 4 | @hook.command 5 | def shorten(inp): 6 | "shorten <url> - Makes an is.gd shortlink to the url provided." 7 | 8 | try: 9 | return web.isgd(inp) 10 | except (web.ShortenError, http.HTTPError) as error: 11 | return error 12 | -------------------------------------------------------------------------------- /plugins/_unused/steam_calc.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, text 2 | import csv 3 | import StringIO 4 | 5 | gauge_url = "http://www.mysteamgauge.com/search?username={}" 6 | 7 | api_url = "http://mysteamgauge.com/user/{}.csv" 8 | steam_api_url = "http://steamcommunity.com/id/{}/?xml=1" 9 | 10 | 11 | def refresh_data(name): 12 | http.get(gauge_url.format(name), timeout=25, get_method='HEAD') 13 | 14 | 15 | def get_data(name): 16 | return http.get(api_url.format(name)) 17 | 18 | 19 | def is_number(s): 20 | try: 21 | float(s) 22 | return True 23 | except ValueError: 24 | return False 25 | 26 | 27 | def unicode_dictreader(utf8_data, **kwargs): 28 | csv_reader = csv.DictReader(utf8_data, **kwargs) 29 | for row in csv_reader: 30 | yield dict([(key.lower(), unicode(value, 'utf-8')) for key, value in row.iteritems()]) 31 | 32 | 33 | @hook.command('sc') 34 | @hook.command 35 | def steamcalc(inp, reply=None): 36 | """steamcalc <username> [currency] - Gets value of steam account and 37 | total hours played. Uses steamcommunity.com/id/<nickname>. """ 38 | 39 | name = inp.strip() 40 | 41 | try: 42 | request = get_data(name) 43 | do_refresh = True 44 | except (http.HTTPError, http.URLError): 45 | try: 46 | reply("Collecting data, this may take a while.") 47 | refresh_data(name) 48 | request = get_data(name) 49 | do_refresh = False 50 | except (http.HTTPError, http.URLError): 51 | return "Could not get data for this user." 52 | 53 | csv_data = StringIO.StringIO(request) # we use StringIO because CSV can't read a string 54 | reader = unicode_dictreader(csv_data) 55 | 56 | # put the games in a list 57 | games = [] 58 | for row in reader: 59 | games.append(row) 60 | 61 | data = {} 62 | 63 | # basic information 64 | steam_profile = http.get_xml(steam_api_url.format(name)) 65 | try: 66 | data["name"] = steam_profile.find('steamID').text 67 | online_state = steam_profile.find('stateMessage').text 68 | except AttributeError: 69 | return "Could not get data for this user." 70 | 71 | online_state = online_state.replace("<br/>", ": ") # will make this pretty later 72 | data["state"] = text.strip_html(online_state) 73 | 74 | # work out the average metascore for all games 75 | ms = [float(game["metascore"]) for game in games if is_number(game["metascore"])] 76 | metascore = float(sum(ms)) / len(ms) if len(ms) > 0 else float('nan') 77 | data["average_metascore"] = "{0:.1f}".format(metascore) 78 | 79 | # work out the totals 80 | data["games"] = len(games) 81 | 82 | total_value = sum([float(game["value"]) for game in games if is_number(game["value"])]) 83 | data["value"] = str(int(round(total_value))) 84 | 85 | # work out the total size 86 | total_size = 0.0 87 | 88 | for game in games: 89 | if not is_number(game["size"]): 90 | continue 91 | 92 | if game["unit"] == "GB": 93 | total_size += float(game["size"]) 94 | else: 95 | total_size += float(game["size"]) / 1024 96 | 97 | data["size"] = "{0:.1f}".format(total_size) 98 | 99 | reply("{name} ({state}) has {games} games with a total value of ${value}" 100 | " and a total size of {size}GB! The average metascore for these" 101 | " games is {average_metascore}.".format(**data)) 102 | 103 | if do_refresh: 104 | refresh_data(name) 105 | -------------------------------------------------------------------------------- /plugins/amazon.py: -------------------------------------------------------------------------------- 1 | # amazon plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | import re 6 | 7 | 8 | def parse(html): 9 | soup = BeautifulSoup(html, 'lxml') 10 | container = soup.find(attrs={'data-component-type': 's-search-results'}) 11 | if container is None: 12 | return [] 13 | 14 | results = container.find_all(attrs={'data-component-type': 's-search-result'}) 15 | 16 | if len(results) == 0: 17 | return [] 18 | 19 | links = [] 20 | for result in results: 21 | title = result.find('h2') 22 | price = result.find('span', attrs={'class': 'a-offscreen'}) 23 | 24 | if title is None or price is None: 25 | continue 26 | 27 | id = result['data-asin'] 28 | title = title.text.strip() 29 | price = price.text.strip() 30 | url = 'https://www.amazon.com/dp/' + id + '/' 31 | 32 | # avoids spam if they change urls in the future 33 | if len(id) > 20: 34 | continue 35 | 36 | links.append((title, price, url)) 37 | 38 | return links 39 | 40 | 41 | def parse_product(html): 42 | soup = BeautifulSoup(html, 'lxml') 43 | title = soup.find(id='productTitle') 44 | price = soup.find(id='priceblock_ourprice') 45 | 46 | if title is None: 47 | title = soup.find('title') 48 | 49 | if title is None: 50 | title = 'Untitled' 51 | 52 | title = title.text.replace('Amazon.com: ', '') 53 | else: 54 | title = title.text.strip() 55 | 56 | if price is None: 57 | price = 'various prices' 58 | else: 59 | price = price.text.strip() 60 | 61 | return title, price 62 | 63 | 64 | @hook.command 65 | def amazon(inp): 66 | """amazon [query] -- Searches amazon for query""" 67 | if not inp: 68 | return "usage: amazon <search>" 69 | 70 | inp = request.urlencode(inp) 71 | html = request.get('https://www.amazon.com/s?k=' + inp) 72 | results = parse(html) 73 | 74 | if len(results) == 0: 75 | return 'No results found' 76 | 77 | title, price, url = results[0] 78 | 79 | if len(title) > 80: 80 | title = title[:80] + '...' 81 | 82 | # \x03 = color, 03 = green 83 | return u'[Amazon] {} \x0303{}\x03 {}'.format(title, price, url) 84 | 85 | 86 | AMAZON_RE = (r"https?:\/\/(www\.)?amazon.com\/[^\s]*dp\/([A-Za-z0-9]+)[^\s]*", re.I) 87 | 88 | 89 | @hook.regex(*AMAZON_RE) 90 | def amazon_url(match): 91 | id = match.group(2).strip() 92 | url = 'https://www.amazon.com/dp/' + id + '/' 93 | html = request.get(url) 94 | title, price = parse_product(html) 95 | 96 | if len(title) > 80: 97 | title = title[:80] + '...' 98 | 99 | return u'[Amazon] {} \x0303{}\x03 {}'.format(title, price, url) 100 | -------------------------------------------------------------------------------- /plugins/bash.py: -------------------------------------------------------------------------------- 1 | # bash.org plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | 6 | cache = [] 7 | 8 | 9 | def refresh_cache(): 10 | "gets a page of random bash.org quotes and puts them into a dictionary " 11 | print "[+] refreshing bash cache" 12 | html = request.get('http://bash.org/?random') 13 | soup = BeautifulSoup(html, 'lxml') 14 | quote_infos = soup.find_all('p', {'class': 'quote'}) 15 | quotes = soup.find_all('p', {'class': 'qt'}) 16 | 17 | num = 0 18 | while num < len(quotes): 19 | quote = quotes[num].text.replace('\n', ' ').replace('\r', ' |') 20 | id = quote_infos[num].contents[0].text 21 | votes = quote_infos[num].find('font').text 22 | cache.append((id, votes, quote)) 23 | num += 1 24 | 25 | 26 | def get_bash_quote(inp): 27 | try: 28 | inp = request.urlencode(inp) 29 | html = request.get('http://bash.org/?' + inp) 30 | soup = BeautifulSoup(html, 'lxml') 31 | quote_info = soup.find('p', {'class': 'quote'}) 32 | quote = soup.find('p', {'class': 'qt'}).text.replace('\n', ' ').replace('\r', ' |') 33 | 34 | id = quote_info.contents[0].text 35 | votes = quote_info.find('font').text 36 | return u'\x02{}\x02 ({} votes): {}'.format(id, votes, quote) 37 | except: 38 | return "No quote found." 39 | 40 | 41 | @hook.command(autohelp=False) 42 | def bash(inp, reply=None): 43 | "bash <id> -- Gets a random quote from Bash.org, or returns a specific id." 44 | if inp: 45 | return get_bash_quote(inp) 46 | 47 | id, votes, text = cache.pop() 48 | if len(cache) < 3: 49 | refresh_cache() 50 | 51 | return u'\x02{}\x02 ({} votes): {}'.format(id, votes, text) 52 | 53 | 54 | refresh_cache() 55 | -------------------------------------------------------------------------------- /plugins/choose.py: -------------------------------------------------------------------------------- 1 | import random 2 | from util import hook 3 | 4 | 5 | @hook.command('decide') 6 | @hook.command 7 | def choose(inp): 8 | "choose <choice1>, [choice2], [choice3], ... -- Randomly picks one of the given choices." 9 | 10 | replacewords = {'should', 'could', '?', ' i ', ' you '} 11 | 12 | for word in replacewords: 13 | inp = inp.replace(word, '') 14 | 15 | if ':' in inp: 16 | inp = inp.split(':')[1] 17 | 18 | c = inp.split(', ') 19 | if len(c) == 1: 20 | c = inp.split(' or ') 21 | if len(c) == 1: 22 | c = ['Yes', 'No'] 23 | 24 | return random.choice(c).strip() 25 | -------------------------------------------------------------------------------- /plugins/coin.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import random 3 | 4 | 5 | @hook.command(autohelp=False) 6 | def coin(inp, me=None): 7 | """coin [amount] -- Flips [amount] of coins.""" 8 | 9 | if inp: 10 | try: 11 | amount = int(inp) 12 | except (ValueError, TypeError): 13 | return "Invalid input!" 14 | else: 15 | amount = 1 16 | 17 | if amount > 1000: 18 | return "thats too many coins" 19 | 20 | if amount == 1: 21 | me("flips a coin and gets {}.".format(random.choice(["heads", "tails"]))) 22 | elif amount == 0: 23 | me("makes a coin flipping motion with its hands.") 24 | else: 25 | heads = int(random.normalvariate(0.5 * amount, (0.75 * amount) ** 0.5)) 26 | tails = amount - heads 27 | me("flips {} coins and gets {} heads and {} tails.".format(amount, heads, tails)) 28 | -------------------------------------------------------------------------------- /plugins/coins.py: -------------------------------------------------------------------------------- 1 | # crypto coins plugin by ine 2 | import json 3 | from util import hook 4 | from utilities import request 5 | 6 | # update this like once a month 7 | # https://api.coingecko.com/api/v3/coins/list 8 | json_data = open("plugins/data/coingecko-coins.json", "r") 9 | supported_coins = json.loads(json_data.read()) 10 | json_data.close() 11 | 12 | 13 | base_url = 'https://api.coingecko.com/api/v3/coins/' 14 | query_string = 'localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false' 15 | 16 | 17 | def consume_api(id): 18 | json = request.get_json(base_url + id + '?' + query_string) 19 | return json 20 | 21 | 22 | def find_coin_id(inp): 23 | for coin in supported_coins: 24 | # it could be "if inp in coin:" but i want to search name (Bitcoin) 25 | # or code (BTC) only, because coingecko ids are nasty 26 | coin_id = coin[0] 27 | coin_symbol = coin[1] 28 | coin_name = coin[2] 29 | 30 | # dumb api sometimes returns nothing 31 | if "" in coin: 32 | continue 33 | 34 | # match name/symbol only 35 | if inp.lower() == coin_name.lower() or inp.lower() == coin_symbol: 36 | return coin 37 | 38 | return False 39 | 40 | 41 | @hook.command('cg', autohelp=False) 42 | @hook.command(autohelp=False) 43 | def cryptocoin(inp): 44 | if inp == "": 45 | return "[coin] Search a coin with .cryptocoin <name> (or .cg eth)" 46 | 47 | coin = find_coin_id(inp) 48 | 49 | if coin is False: 50 | return "[coin] cryptocoin " + inp + " not found" 51 | 52 | data = consume_api(coin[0]) 53 | 54 | # change this to support other (real) coins like eur, jpy, gbp, nok 55 | real_coin = 'usd' 56 | current = data['market_data']['current_price'][real_coin] 57 | high = data['market_data']['high_24h'][real_coin] 58 | low = data['market_data']['low_24h'][real_coin] 59 | volume = data['market_data']['total_volume'][real_coin] 60 | cap = data['market_data']['market_cap'][real_coin] 61 | change_24h = data['market_data']['price_change_percentage_24h'] 62 | change_7d = data['market_data']['price_change_percentage_7d'] 63 | # change_14d = data['market_data']['price_change_percentage_14d'] 64 | change_30d = data['market_data']['price_change_percentage_30d'] 65 | #change_60d = data['market_data']['price_change_percentage_60d'] 66 | #change_200d = data['market_data']['price_change_percentage_200d'] 67 | 68 | output = "[coin] {} ({}) Current: \x0307${:,}\x03, High: \x0307${:,}\x03, Low: \x0307${:,}\x03, Vol: ${:,}, Cap: ${:,}".format(coin[2], coin[1].upper(), current, high, low, volume, cap) 69 | 70 | if change_24h < 0: 71 | output = output + ", 24h: \x0304{:.2f}%\x03".format(change_24h) 72 | else: 73 | output = output + ", 24h: \x0303+{:.2f}%\x03".format(change_24h) 74 | 75 | if change_7d < 0: 76 | output = output + ", 7d: \x0304{:.2f}%\x03".format(change_7d) 77 | else: 78 | output = output + ", 7d: \x0303+{:.2f}%\x03".format(change_7d) 79 | 80 | if change_30d < 0: 81 | output = output + ", 30d: \x0304{:.2f}%\x03".format(change_30d) 82 | else: 83 | output = output + ", 30d: \x0303+{:.2f}%\x03".format(change_30d) 84 | 85 | return output 86 | 87 | 88 | @hook.command('bitcoin', autohelp=False) 89 | @hook.command(autohelp=False) 90 | def btc(inp): 91 | return cryptocoin('bitcoin') 92 | 93 | @hook.command('ethereum', autohelp=False) 94 | @hook.command(autohelp=False) 95 | def eth(inp): 96 | return cryptocoin('ethereum') 97 | 98 | 99 | # <wednesday> .doge 100 | # <Taigabot> Error: Doge is worthless. 101 | # <wednesday> inex: keep .doge 102 | # <wednesday> I like that 103 | @hook.command(autohelp=False) 104 | def doge(inp): 105 | return "Error: Doge is worthless." 106 | -------------------------------------------------------------------------------- /plugins/countdown.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import time 3 | 4 | countdown_is_running = False 5 | countdown_nicks = [] 6 | countdown_timeout = 20 7 | 8 | 9 | def set_countdown_to_true(): 10 | global countdown_is_running 11 | countdown_is_running = True 12 | 13 | 14 | def set_countdown_to_false(): 15 | global countdown_is_running 16 | countdown_is_running = False 17 | 18 | 19 | @hook.command(autohelp=False) 20 | def countdown(inp, me=None): 21 | "countdown [seconds] [nick1 nick2 nick3] -- starts a countdown. It will begin when all the users type .ready" 22 | 23 | if countdown_is_running: 24 | return 25 | else: 26 | set_countdown_to_true() 27 | 28 | global countdown_nicks 29 | wait_count = 1 30 | inp = inp.lower().replace(',', '') 31 | count = 6 32 | 33 | try: 34 | if inp[0][0].isdigit(): 35 | count = int(inp.split()[0]) + 1 36 | countdown_nicks = inp.split()[1:] 37 | if count > 6: 38 | count = 6 39 | else: 40 | countdown_nicks = inp.split()[0:] 41 | count = 6 42 | except Exception: 43 | pass 44 | 45 | if len(inp) > 6: 46 | nicks = ', '.join(countdown_nicks) 47 | me('Countdown started! Waiting for {}. Type \x02.ready\x02 when ready!'.format(nicks)) 48 | 49 | # TODO fix: there can be only one countdown running per network 50 | # this chunk keeps the whole plugin busy 51 | while countdown_nicks: 52 | time.sleep(1) 53 | wait_count = int(wait_count) + 1 54 | if wait_count == countdown_timeout: 55 | set_countdown_to_false() 56 | return "Countdown expired." 57 | 58 | me('Ready! The countdown will begin in 2 seconds...') 59 | time.sleep(2) 60 | 61 | for cur in range(1, count): 62 | me('*** {} ***'.format(count - cur)) 63 | time.sleep(1) 64 | else: 65 | set_countdown_to_false() 66 | return '\x02***\x02 GO \x02***\x02' 67 | 68 | 69 | @hook.command(autohelp=False) 70 | def ready(inp, me=None, nick=None): 71 | "ready -- when all users are ready the countdown will begin." 72 | global countdown_nicks 73 | nicks_size_start = len(countdown_nicks) 74 | try: 75 | countdown_nicks.remove(nick.lower()) 76 | except Exception: 77 | pass 78 | 79 | if nicks_size_start > len(countdown_nicks): 80 | if len(countdown_nicks) > 0: 81 | me('Waiting for: ' + ', '.join(countdown_nicks)) 82 | -------------------------------------------------------------------------------- /plugins/cypher.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Plugin which (de)cyphers a string 3 | Doesn't cypher non-alphanumeric strings yet. 4 | by instanceoftom 5 | ''' 6 | 7 | from util import hook 8 | chars = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ " 9 | len_chars = len(chars) 10 | 11 | 12 | @hook.command 13 | def cypher(inp): 14 | "cypher <pass> <string> -- Cyphers <string> with <password>." 15 | 16 | passwd = inp.split(" ")[0] 17 | len_passwd = len(passwd) 18 | inp = " ".join(inp.split(" ")[1:]) 19 | 20 | out = "" 21 | passwd_index = 0 22 | for character in inp: 23 | try: 24 | chr_index = chars.index(character) 25 | passwd_chr_index = chars.index(passwd[passwd_index]) 26 | 27 | out_chr_index = (chr_index + passwd_chr_index) % len_chars 28 | out_chr = chars[out_chr_index] 29 | 30 | out += out_chr 31 | 32 | passwd_index = (passwd_index + 1) % len_passwd 33 | except ValueError: 34 | out += character 35 | continue 36 | return out 37 | 38 | 39 | @hook.command 40 | def decypher(inp): 41 | "decypher <pass> <string> -- Decyphers <string> with <password>." 42 | 43 | passwd = inp.split(" ")[0] 44 | len_passwd = len(passwd) 45 | inp = " ".join(inp.split(" ")[1:]) 46 | 47 | passwd_index = 0 48 | for character in inp: 49 | try: 50 | chr_index = chars.index(character) 51 | passwd_index = (passwd_index + 1) % len_passwd 52 | except ValueError: 53 | continue 54 | 55 | passwd_index = passwd_index - 1 56 | reversed_message = inp[::-1] 57 | 58 | out = "" 59 | for character in reversed_message: 60 | try: 61 | chr_index = chars.index(character) 62 | passwd_chr_index = chars.index(passwd[passwd_index]) 63 | 64 | out_chr_index = (chr_index - passwd_chr_index) % len_chars 65 | out_chr = chars[out_chr_index] 66 | 67 | out += out_chr 68 | 69 | passwd_index = (passwd_index - 1) % len_passwd 70 | except ValueError: 71 | out += character 72 | continue 73 | 74 | return out[::-1] 75 | -------------------------------------------------------------------------------- /plugins/data/8ball_responses.txt: -------------------------------------------------------------------------------- 1 | <g>As I see it, yes 2 | <g>It is certain 3 | <g>It is decidedly so 4 | <g>Most likely 5 | <g>Outlook good 6 | <g>Signs point to yes 7 | <g>One would be wise to think so 8 | <g>Naturally 9 | <g>Without a doubt 10 | <g>Yes 11 | <g>Yes, definitely 12 | <g>You may rely on it 13 | <y>Reply hazy, try again 14 | <y>Ask again later 15 | <y>Better not tell you now 16 | <y>Cannot predict now 17 | <y>Concentrate and ask again 18 | <y>You know the answer better than I 19 | <y>Maybe... 20 | <r>You're kidding, right? 21 | <r>Don't count on it 22 | <r>In your dreams 23 | <r>My reply is no 24 | <r>My sources say no 25 | <r>Outlook not so good 26 | <r>Very doubtful 27 | -------------------------------------------------------------------------------- /plugins/data/gainz.txt: -------------------------------------------------------------------------------- 1 | http://f.cl.ly/items/2D1S3p3S3H0M2u3Y0q2d/1503814_724675280890626_1044295881_n1.jpg 2 | http://cdn.memegenerator.net/instances/500x/43583042.jpg 3 | http://img2.wikia.nocookie.net/__cb20130801222850/fma/images/a/a9/Muscles.png 4 | http://i.imgur.com/ffzFg.jpg 5 | http://www.bodybuilding-motivation.org/upload/images/you_gotta_love_them_gainz_2013-09-30_20-23-05_middle.jpg 6 | http://www.rottenecards.com/ecards/Rottenecards_94881668_7gszrj9hpm.png 7 | http://38.media.tumblr.com/ab7d653b2cb02880e0488852f9b51631/tumblr_mz754bS2yo1qkbxb1o1_1280.jpg 8 | http://a.pomf.se/dwvjmy.jpg 9 | -------------------------------------------------------------------------------- /plugins/data/keks.txt: -------------------------------------------------------------------------------- 1 | bursts out laughing 2 | keks internally 3 | laughs so hard her vagina splits 4 | http://a.pomf.se/unnhex.jpg 5 | http://a.pomf.se/hvnnqj.png 6 | http://i.infin.me/image/272o3O3i2l2U 7 | http://a.pomf.se/ybnjcf.gif 8 | http://i.infin.me/image/3d1B2J2Q1n10 9 | http://a.pomf.se/o443k.gifhue 10 | http://i0.kym-cdn.com/photos/images/facebook/001/001/369/6a2.png 11 | jajaja queso 12 | quek 13 | https://i.4cdn.org/g/1547969156106.jpg 14 | https://my.mixtape.moe/mkzrqq.jpg 15 | -------------------------------------------------------------------------------- /plugins/data/kills.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates":[ 3 | "rips off {user}'s {limbs} and leaves them to die.", 4 | "grabs {user}'s head and rips it clean off their body.", 5 | "grabs a {gun} and riddles {user}'s body with bullets.", 6 | "gags and ties {user} then throws them off a {tall_thing}.", 7 | "crushes {user} with a huge spiked {spiked_thing}.", 8 | "glares at {user} until they die of boredom.", 9 | "stabs {user} in the heart a few times with a {weapon_stab}.", 10 | "rams a {weapon_explosive} up {user}'s ass and lets off a few rounds.", 11 | "crushes {user}'s skull in with a {weapon_crush}.", 12 | "unleashes the armies of Isengard on {user}.", 13 | "gags and ties {user} then throws them off a {tall_thing} to their death.", 14 | "reaches out and punches right through {user}'s chest.", 15 | "slices {user}'s limbs off with a {weapon_slice}.", 16 | "throws {user} to Cthulu and watches them get ripped to shreds.", 17 | "feeds {user} to an owlbear who then proceeds to maul them violently.", 18 | "turns {user} into a snail and covers then in salt.", 19 | "snacks on {user}'s dismembered body.", 20 | "stuffs {bomb} up {user}'s ass and waits for it to go off.", 21 | "puts {user} into a sack, throws the sack in the river, and hurls the river into space.", 22 | "goes bowling with {user}'s bloody disembodied head.", 23 | "sends {user} to /dev/null!", 24 | "feeds {user} coke and mentos till they violently explode." 25 | ], 26 | "parts": { 27 | "gun":[ 28 | "AK47", 29 | "machine gun", 30 | "automatic pistol", 31 | "Uzi" 32 | ], 33 | "limbs": [ 34 | "legs", 35 | "arms", 36 | "limbs" 37 | ], 38 | "weapon_stab": [ 39 | "knife", 40 | "shard of glass", 41 | "sword blade", 42 | "butchers knife", 43 | "corkscrew" 44 | ], 45 | "weapon_slice": [ 46 | "sharpened katana", 47 | "chainsaw", 48 | "polished axe" 49 | ], 50 | "weapon_crush": [ 51 | "spiked mace", 52 | "baseball bat", 53 | "wooden club", 54 | "massive steel ball", 55 | "heavy iron rod" 56 | ], 57 | "weapon_explosive": [ 58 | "rocket launcher", 59 | "grenade launcher", 60 | "napalm launcher" 61 | ], 62 | "tall_thing": [ 63 | "bridge", 64 | "tall building", 65 | "cliff", 66 | "mountain" 67 | ], 68 | "spiked_thing": [ 69 | "boulder", 70 | "rock", 71 | "barrel of rocks" 72 | ], 73 | "bomb": [ 74 | "a bomb", 75 | "some TNT", 76 | "a bunch of C4" 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /plugins/data/kills.txt: -------------------------------------------------------------------------------- 1 | rips off {user}'s legs and leaves them to die. 2 | grabs {user}'s head and rips it clean off their body. 3 | grabs a machine gun and riddles {user}'s body with bullets. 4 | gags and ties {user} then throws them off a bridge. 5 | crushes {user} with a huge spiked boulder. 6 | glares at {user} until they die of boredom. 7 | shivs {user} in the heart a few times. 8 | rams a rocket launcher up {user}'s ass and lets off a few rounds. 9 | crushes {user}'s skull in with a spiked mace. 10 | unleashes the armies of Isengard on {user}. 11 | gags and ties {user} then throws them off a building to their death. 12 | reaches out and punches right through {user}'s chest. 13 | slices {user}'s limbs off with a sharpened Katana. 14 | throws {user} to Cthulu and watches them get ripped to shreds. 15 | feeds {user} to an owlbear who then proceeds to maul them violently. 16 | turns {user} into a snail and salts them. 17 | snacks on {user}'s dismembered body. 18 | stuffs some TNT up {user}'s ass and waits for it to go off. 19 | puts {user} into a sack, throws the sack in the river, and hurls the river into space. 20 | goes bowling with {user}'s bloody disembodied head. 21 | sends {user} to /dev/null! 22 | feeds {user} coke and mentos till they violently explode. 23 | -------------------------------------------------------------------------------- /plugins/data/lewd.txt: -------------------------------------------------------------------------------- 1 | aims and shoots, but misses {user}'s mouth, leaving a mess of jizz dripping down {user}'s chin ! 2 | aims for {user} but hits {user} instead. 3 | alternates squeezing {user}, pushing on his shoulders as he slips out so he stays inside of her, moaning as he rubs around inside of her 4 | becomes one with the spirit of bukkake and summons a nether demon with 40 odd tentacles to rape and bury {user} in tons of jizz. 5 | bites off {user}'s tongue with his sheath because it has sheathteeth. 6 | blasts a hot one all over {user}'s face, then wipes his cock on {user}'s tongue! 7 | blasts hot cum all over {user} 8 | blushes again and looks down, noticing the slight bulge starting to form in {user}'s pants 9 | bucks her hips into {user}'s mouth as he touches her clit, and starts to spin her butt around on the floor causing his finger to slip in and out of her pussy as she moans and pants 10 | busts a nut in {user}'s eye: 8====D~~~~~~~~~~~0_O 11 | calls {user} a huge fucking faggot 12 | climbs up {user} and sits on his shoulders, her legs draped across his chest, her crotch pressed to the back of his neck 13 | cuddles close to {user}, his excitement poking {user} from behind 14 | cums all over {user} ! 15 | darts inside of {user}'s shirt, licking his chest on her way up, and pops her head out of the collar of his shirt, sitting on his lap with her back to his stomach 16 | does something disgusting to {user} 17 | does the unthinkable to {user} 18 | farts all over {user}'s dick 19 | feels a gush of her juices rush out into {user}'s licking mouth as she spazms on the floor in front of him, her body quivering with her orgasm 20 | feels some of her juice leak out of her squeezed pussy lips onto {user}'s hands "mhmm, feels nice {user} =)" 21 | feels {user} fall under her as she sits up, squishing him between the lips of her pussy and the chair 22 | feels {user} pull out of his ass and spins around to take the sticky sweet load in his face 23 | finishes peeing and pulls up her panties under her skirt, turns around, and wonders why {user} is still watching her 24 | gets down on all fours and humps {user} 25 | gets down with {user}, doggie style 26 | giggles as he coats {user} in a thick jizzum, who then quickly charges over and mounts {nick} out of vengence 27 | giggles as {user} tickles her feet, and withdraws them instinctively towards he chest, her dress falling down to her waist, giving {user} a look at her white cotton panties 28 | gives {user} a kiss on teh lips, tossing some tongue in there before {user} can stop her, and runs to the door 29 | gives {user} a pearl necklace. 30 | glues {user}'s eyes shut with a load a sperm in the eye! 31 | goes apeshit and jumps on {user} with a pulsing hard cock 32 | grinds her hips in to {user}'s hand as she moans, "mmmm i like the way you do that uncle" 33 | grinds her pantie-covered crotch against the back of {user}'s neck 34 | hugs {user} and puts his hand on her crotch, saying, "you'd better look at it though, to make sure it's not damaged" 35 | ironically turns around and wonders why {user} and {yiffer} are behind the same bush she's peeing behind 36 | is too lazy to shoot a load on {user} so he beats {user} with a rubber dildo instead. 37 | jizzes all over {user}'s face! 38 | -------------------------------------------------------------------------------- /plugins/data/lewds.txt: -------------------------------------------------------------------------------- 1 | grinds his boypussy on {user}'s erect 13,11PENIS 2 | pomfs {user} onto a bed and handcuffs them to the post. Uguubot then grinds her moist panties up againt {user} 's face and says, "Well it ain't gonna lick itself 3 | pomfs {user} onto a bed and handcuffs them to the post. Uguubot then grinds her moist panties up againt {user} 's face and says, "Well it ain't gonna lick itself" 4 | 5 | whips out $username's dick 6 | le hace la cola a Fest 7 | fug 8 | 9 | licks {user}'s colon 10 | -------------------------------------------------------------------------------- /plugins/data/moists.txt: -------------------------------------------------------------------------------- 1 | moistens you...."Don't worry about what I'm moistening you with, baby..." 2 | rubs a moistening substance on your tight, young body..."You're so sexy when you're moist..." 3 | moistens you, eyes looking up at you for approval..."Did I moisten you well, master?"That's it, I'm getting the hose! 4 | slowly rubs the soft, moisturizing lotion onto your tender venus cleft after warming it up on her breasts 5 | -------------------------------------------------------------------------------- /plugins/data/name_files/dwarves.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dwarven names", 3 | "author": "Johan Danforth", 4 | "templates": { 5 | "default": "{first}{mid}{final}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "final": [ 12 | "bur", 13 | "fur", 14 | "gan", 15 | "gnus", 16 | "gnar", 17 | "li", 18 | "lin", 19 | "lir", 20 | "mli", 21 | "nar", 22 | "nus", 23 | "rin", 24 | "ran", 25 | "sin", 26 | "sil", 27 | "sur" 28 | ], 29 | "mid": [ 30 | "a", 31 | "e", 32 | "i", 33 | "o", 34 | "oi", 35 | "u" 36 | ], 37 | "first": [ 38 | "B", 39 | "D", 40 | "F", 41 | "G", 42 | "Gl", 43 | "H", 44 | "K", 45 | "L", 46 | "M", 47 | "N", 48 | "R", 49 | "S", 50 | "T", 51 | "V" 52 | ] 53 | } 54 | } -------------------------------------------------------------------------------- /plugins/data/name_files/elves_female.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Elven female names", 3 | "author": "Johan Danforth", 4 | "templates": { 5 | "default": "{first}{mid}{final}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "final": [ 12 | "clya", 13 | "lindi", 14 | "di", 15 | "dien", 16 | "dith", 17 | "dia", 18 | "lith", 19 | "lia", 20 | "ndra", 21 | "ng", 22 | "nia", 23 | "niel", 24 | "rith", 25 | "thien", 26 | "thiel", 27 | "viel", 28 | "wen", 29 | "wien", 30 | "wiel" 31 | ], 32 | "mid": [ 33 | "a", 34 | "a", 35 | "adrie", 36 | "ara", 37 | "e", 38 | "e", 39 | "ebri", 40 | "i", 41 | "io", 42 | "ithra", 43 | "ilma", 44 | "il-Ga", 45 | "o", 46 | "orfi", 47 | "o", 48 | "u", 49 | "y" 50 | ], 51 | "first": [ 52 | "An", 53 | "Am", 54 | "Bel", 55 | "Cel", 56 | "C", 57 | "Cal", 58 | "Del", 59 | "El", 60 | "Elr", 61 | "Elv", 62 | "Eow", 63 | "Ear", 64 | "F", 65 | "G", 66 | "Gal", 67 | "Gl", 68 | "H", 69 | "Is", 70 | "Leg", 71 | "Lem", 72 | "M", 73 | "N", 74 | "P", 75 | "R", 76 | "S", 77 | "T", 78 | "Thr", 79 | "Tin", 80 | "Ur", 81 | "Un", 82 | "V" 83 | ] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /plugins/data/name_files/elves_male.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Elven male names", 3 | "author": "Johan Danforth", 4 | "templates": { 5 | "default": "{first}{mid}{final}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "final": [ 12 | "l", 13 | "las", 14 | "lad", 15 | "ldor", 16 | "ldur", 17 | "lith", 18 | "mir", 19 | "n", 20 | "nd", 21 | "ndel", 22 | "ndil", 23 | "ndir", 24 | "nduil", 25 | "ng", 26 | "mbor", 27 | "r", 28 | "ril", 29 | "riand", 30 | "rion", 31 | "wyn" 32 | ], 33 | "mid": [ 34 | "a", 35 | "a", 36 | "adrie", 37 | "ara", 38 | "e", 39 | "e", 40 | "ebri", 41 | "i", 42 | "io", 43 | "ithra", 44 | "ilma", 45 | "il-Ga", 46 | "o", 47 | "orfi", 48 | "o", 49 | "u", 50 | "y" 51 | ], 52 | "first": [ 53 | "An", 54 | "Am", 55 | "Bel", 56 | "Cel", 57 | "C", 58 | "Cal", 59 | "Del", 60 | "El", 61 | "Elr", 62 | "Elv", 63 | "Eow", 64 | "Ear", 65 | "F", 66 | "G", 67 | "Gal", 68 | "Gl", 69 | "H", 70 | "Is", 71 | "Leg", 72 | "Lem", 73 | "M", 74 | "N", 75 | "P", 76 | "R", 77 | "S", 78 | "T", 79 | "Thr", 80 | "Tin", 81 | "Ur", 82 | "Un", 83 | "V" 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /plugins/data/name_files/hobbits.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "name": "Tolkien hobbit names", 4 | "author": "Johan Danforth", 5 | "templates": { 6 | "default": "{first}{mid}{final}" 7 | }, 8 | "default_templates": [ 9 | "default" 10 | ], 11 | "parts": { 12 | "final": [ 13 | "bo", 14 | "do", 15 | "doc", 16 | "go", 17 | "grin", 18 | "m" 19 | ], 20 | "mid": [ 21 | "a", 22 | "e", 23 | "i", 24 | "ia", 25 | "o", 26 | "oi", 27 | "u" 28 | ], 29 | "first": [ 30 | "B", 31 | "Dr", 32 | "Fr", 33 | "Mer", 34 | "Per", 35 | "S" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugins/data/name_files/inns.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Inn/Tavern/Bar/Pub Names", 3 | "author": "Kimmo \"Arkhan\" Kulovesi", 4 | "templates": { 5 | "default": "{start} {end}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "end": [ 12 | "Axe", 13 | "Barrel", 14 | "Basilisk", 15 | "Belly", 16 | "Blade", 17 | "Boar", 18 | "Breath", 19 | "Brew", 20 | "Busom", 21 | "Claw", 22 | "Coin", 23 | "Delight", 24 | "Den", 25 | "Dragon", 26 | "Drum", 27 | "Dwarf", 28 | "Fist", 29 | "Flea", 30 | "Flower", 31 | "Gem", 32 | "Gryphon", 33 | "Hand", 34 | "Head", 35 | "Inn", 36 | "Lady", 37 | "Maiden", 38 | "Lantern", 39 | "Lips", 40 | "Monk", 41 | "Mug", 42 | "Nest", 43 | "Orc", 44 | "Pearl", 45 | "Pig", 46 | "Pit", 47 | "Place", 48 | "Tavern", 49 | "Portal", 50 | "Ranger", 51 | "Rest", 52 | "Sailor", 53 | "Sleep", 54 | "Song", 55 | "Stool", 56 | "Swan", 57 | "Swords", 58 | "Tree", 59 | "Unicorn", 60 | "Whale", 61 | "Wish", 62 | "Wizard", 63 | "Rain" 64 | ], 65 | "start": [ 66 | "Bent", 67 | "Black", 68 | "Blind", 69 | "Blue", 70 | "Bob's", 71 | "Joe's", 72 | "Broken", 73 | "Buxom", 74 | "Cat's", 75 | "Crow's", 76 | "Dirty", 77 | "Dragon", 78 | "Dragon's", 79 | "Drunken", 80 | "Eagle's", 81 | "Eastern", 82 | "Falcon's", 83 | "Fawning", 84 | "Fiend's", 85 | "Flaming", 86 | "Frosty", 87 | "Frozen", 88 | "Gilded", 89 | "Genie's", 90 | "Golden", 91 | "Golden", 92 | "Gray", 93 | "Green", 94 | "King's", 95 | "Licked", 96 | "Lion's", 97 | "Mended", 98 | "Octopus", 99 | "Old", 100 | "Old", 101 | "Orc's", 102 | "Otik's", 103 | "Tika's", 104 | "Pink", 105 | "Pot", 106 | "Puking", 107 | "Queen's", 108 | "Red", 109 | "Ruby", 110 | "Delicate", 111 | "Sea", 112 | "Sexy", 113 | "Shining", 114 | "Silver", 115 | "Singing", 116 | "Strange", 117 | "Thirsty", 118 | "Violet", 119 | "White", 120 | "Wild", 121 | "Yawing " 122 | ] 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /plugins/data/name_files/narn.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Babylon 5 Narn names", 3 | "author": "Kevin G. Nunn", 4 | "templates": { 5 | "default": "{first}{mid}{final}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "final": [ 12 | "ch", 13 | "k", 14 | "kk", 15 | "l", 16 | "n", 17 | "r", 18 | "th", 19 | "s" 20 | ], 21 | "mid": [ 22 | "Ba", 23 | "Bo", 24 | "Da", 25 | "Do", 26 | "Ga", 27 | "Ge", 28 | "Go", 29 | "Ka", 30 | "Ko", 31 | "La", 32 | "Le", 33 | "Lo", 34 | "Ma", 35 | "Mo", 36 | "Na", 37 | "No", 38 | "Oo", 39 | "Pa", 40 | "Po", 41 | "Qua", 42 | "Quo", 43 | "Ra", 44 | "Rala", 45 | "Ro", 46 | "Sha", 47 | "Shali", 48 | "Ska", 49 | "Skali", 50 | "Sta", 51 | "Ste", 52 | "Sto", 53 | "Ta", 54 | "Te", 55 | "Tee", 56 | "To", 57 | "Tha", 58 | "Tho", 59 | "Va", 60 | "Vo", 61 | "Vy", 62 | "Wa" 63 | ], 64 | "first": [ 65 | "Ch'", 66 | "Do'", 67 | "G'", 68 | "Gre'", 69 | "Mak'", 70 | "Na'", 71 | "Re'", 72 | "Sh'", 73 | "So'", 74 | "T'", 75 | "Ta'", 76 | "Th'", 77 | "Thu'", 78 | "Tu'" 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /plugins/data/old.txt: -------------------------------------------------------------------------------- 1 | Honrys so old I told him to act his own age, and he died. 2 | Honrys so old he has Jesus' beeper number! 3 | Honrys so old his social security number is 1! 4 | Honrys so old that when God said let the be light, he hit the switch' 5 | Honrys so old that when he was in school there was no history class. 6 | Honrys so old he owes Jesus 3 bucks! 7 | Honrys so old he's in Jesus's yearbook! 8 | Honrys so old he has a picture of Moses in his yearbook. 9 | Honrys so old his birth certificate says expired on it. 10 | Honrys so old he knew Burger King while he was still a prince. 11 | Honrys so old he was a busboy at the Last Supper. 12 | Honrys so old he ran track with dinosaurs. 13 | Honrys so old his birth certificate is in Roman numerals. 14 | Honrys so old he sat behind God in the third grade. 15 | Honrys so old and stupid he knew the Virgin Mary when he was 10 and said, "Li'l Mary will never amount to anything". 16 | Honrys so old hes blind from the big bang 17 | Honrys so old even God calls his daddy! 18 | Honrys so old his ass is a cave for indians.5 19 | -------------------------------------------------------------------------------- /plugins/data/potato.txt: -------------------------------------------------------------------------------- 1 | AC Belmont 2 | AC Blue Pride 3 | AC Brador 4 | AC Chaleur 5 | AC Domino 6 | AC Dubuc 7 | AC Glacier Chip 8 | AC Maple Gold 9 | AC Novachip 10 | AC Peregrine Red 11 | AC Ptarmigan 12 | AC Red Island 13 | AC Saguenor 14 | AC Stampede Russet 15 | AC Sunbury 16 | Abeille 17 | Abnaki 18 | Acadia 19 | Acadia Russet 20 | Accent 21 | Adirondack Blue 22 | Adirondack Red 23 | Adora 24 | Agria 25 | All Blue 26 | All Red 27 | Alpha 28 | Alta Russet 29 | Alturas Russet 30 | Amandine 31 | Amisk 32 | Andover 33 | Anoka 34 | Anson 35 | Aquilon 36 | Arran Consul 37 | Asterix 38 | Atlantic 39 | Austrian Crescent 40 | Avalanche 41 | Banana 42 | Bannock Russet 43 | Batoche 44 | BeRus 45 | Belle De Fonteney 46 | Belleisle 47 | Bintje 48 | Blossom 49 | Blue Christie 50 | Blue Mac 51 | Brigus 52 | Brise du Nord 53 | Butte 54 | Butterfinger 55 | Caesar 56 | CalWhite 57 | CalRed 58 | Caribe 59 | Carlingford 60 | Carlton 61 | Carola 62 | Cascade 63 | Castile 64 | Centennial Russet 65 | Century Russet 66 | Charlotte 67 | Cherie 68 | Cherokee 69 | Cherry Red 70 | Chieftain 71 | Chipeta 72 | Coastal Russet 73 | Colorado Rose 74 | Concurrent 75 | Conestoga 76 | Cowhorn 77 | Crestone Russet 78 | Crispin 79 | Cupids 80 | Daisy Gold 81 | Dakota Pearl 82 | Defender 83 | Delikat 84 | Denali 85 | Desiree 86 | Divina 87 | Dundrod 88 | Durango Red 89 | Early Rose 90 | Elba 91 | Envol 92 | Epicure 93 | Eramosa 94 | Estima 95 | Eva 96 | Fabula 97 | Fambo 98 | Fremont Russet 99 | French Fingerling 100 | Frontier Russet 101 | Fundy 102 | Garnet Chile 103 | Gem Russet 104 | GemStar Russet 105 | Gemchip 106 | German Butterball 107 | Gigant 108 | Goldrush 109 | Granola 110 | Green Mountain 111 | Haida 112 | Hertha 113 | Hilite Russet 114 | Huckleberry 115 | Hunter 116 | Huron 117 | IdaRose 118 | Innovator 119 | Irish Cobbler 120 | Island Sunshine 121 | Ivory Crisp 122 | Jacqueline Lee 123 | Jemseg 124 | Kanona 125 | Katahdin 126 | Kennebec 127 | Kerr's Pink 128 | Keswick 129 | Keuka Gold 130 | Keystone Russet 131 | King Edward VII 132 | Kipfel 133 | Klamath Russet 134 | Krantz 135 | LaRatte 136 | Lady Rosetta 137 | Latona 138 | Lemhi Russet 139 | Liberator 140 | Lili 141 | MaineChip 142 | Marfona 143 | Maris Bard 144 | Maris Piper 145 | Matilda 146 | Mazama 147 | McIntyre 148 | Michigan Purple 149 | Millenium Russet 150 | Mirton Pearl 151 | Modoc 152 | Mondial 153 | Monona 154 | Morene 155 | Morning Gold 156 | Mouraska 157 | Navan 158 | Nicola 159 | Nipigon 160 | Niska 161 | Nooksack 162 | NorValley 163 | Norchip 164 | Nordonna 165 | Norgold Russet 166 | Norking Russet 167 | Norland 168 | Norwis 169 | Obelix 170 | Ozette 171 | Peanut 172 | Penta 173 | Peribonka 174 | Peruvian Purple 175 | Pike 176 | Pink Pearl 177 | Prospect 178 | Pungo 179 | Purple Majesty 180 | Purple Viking 181 | Ranger Russet 182 | Reba 183 | Red Cloud 184 | Red Gold 185 | Red La Soda 186 | Red Pontiac 187 | Red Ruby 188 | Red Thumb 189 | Redsen 190 | Rocket 191 | Rose Finn Apple 192 | Rose Gold 193 | Roselys 194 | Rote Erstling 195 | Ruby Crescent 196 | Russet Burbank 197 | Russet Legend 198 | Russet Norkotah 199 | Russet Nugget 200 | Russian Banana 201 | Saginaw Gold 202 | Sangre 203 | Sant� 204 | Satina 205 | Saxon 206 | Sebago 207 | Shepody 208 | Sierra 209 | Silverton Russet 210 | Simcoe 211 | Snowden 212 | Spunta 213 | St. John's 214 | Summit Russet 215 | Sunrise 216 | Superior 217 | Symfonia 218 | Tolaas 219 | Trent 220 | True Blue 221 | Ulla 222 | Umatilla Russet 223 | Valisa 224 | Van Gogh 225 | Viking 226 | Wallowa Russet 227 | Warba 228 | Western Russet 229 | White Rose 230 | Willamette 231 | Winema 232 | Yellow Finn 233 | Yukon Gold 234 | -------------------------------------------------------------------------------- /plugins/data/qts.txt: -------------------------------------------------------------------------------- 1 | ^_^ 2 | =3 3 | ^*^ 4 | ケ≖‿≖)ケ 5 | ヾ(⌐■_■)ノ 6 | ʕ•ᴥ•ʔ 7 | ( ⊃ ^ڡ^ )⊃( ⊃ ^ڡ^ )⊃( ⊃ ^ڡ^ )⊃( ⊃ ^ڡ^ )⊃ 8 | (っ´ω`c) 9 | ( ≖‿≖)( ⊙‿⊙)( ⌒‿⌒)( 0‿0)(ケ≖‿≖)ケ 10 | ヽ(´ー`)ノ(⌐■‿■)(。 ‿°)(⌐◪‿◪)~( ⊙‿⊙)~ 11 | ʘ‿ʘ 12 | └(・ω・└) 13 | (◡‿◡✿) 14 | *pomf* :3 15 | hue 16 | ˙͜>˙ 17 | hue 18 | -------------------------------------------------------------------------------- /plugins/data/slap_items.txt: -------------------------------------------------------------------------------- 1 | cast iron skillet 2 | large trout 3 | baseball bat 4 | wooden cane 5 | CRT monitor 6 | diamond sword 7 | physics textbook 8 | television 9 | mau5head 10 | five ton truck 11 | roll of duct tape 12 | book 13 | cobblestone block 14 | lava bucket 15 | rubber chicken 16 | gold block 17 | fire extinguisher 18 | heavy rock 19 | chunk of dirt 20 | -------------------------------------------------------------------------------- /plugins/data/slaps.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates":[ 3 | "{hits} {user} with a {item}.", 4 | "{hits} {user} around a bit with a {item}.", 5 | "{throws} a {item} at {user}.", 6 | "{throws} a few {item}s at {user}.", 7 | "grabs a {item} and {throws} it in {user}'s face.", 8 | "launches a {item} in {user}'s general direction.", 9 | "sits on {user}'s face while slamming a {item} into their crotch.", 10 | "starts slapping {user} silly with a {item}.", 11 | "holds {user} down and repeatedly {hits} them with a {item}.", 12 | "prods {user} with a {item}.", 13 | "picks up a {item} and {hits} {user} with it.", 14 | "ties {user} to a chair and {throws} a {item} at them.", 15 | "{hits} {user} {where} with a {item}.", 16 | "ties {user} to a pole and whips them with a {item}." 17 | ], 18 | "parts": { 19 | "item":[ 20 | "cast iron skillet", 21 | "large trout", 22 | "baseball bat", 23 | "wooden cane", 24 | "nail", 25 | "printer", 26 | "shovel", 27 | "pair of trousers", 28 | "CRT monitor", 29 | "diamond sword", 30 | "baguette", 31 | "physics textbook", 32 | "toaster", 33 | "portrait of Mark Harmon", 34 | "television", 35 | "mau5head", 36 | "five ton truck", 37 | "roll of duct tape", 38 | "book", 39 | "laptop", 40 | "old television", 41 | "sack of rocks", 42 | "rainbow trout", 43 | "cobblestone block", 44 | "lava bucket", 45 | "rubber chicken", 46 | "spiked bat", 47 | "gold block", 48 | "fire extinguisher", 49 | "heavy rock", 50 | "chunk of dirt" 51 | ], 52 | "throws": [ 53 | "throws", 54 | "flings", 55 | "chucks" 56 | ], 57 | "hits": [ 58 | "hits", 59 | "whacks", 60 | "slaps", 61 | "smacks" 62 | ], 63 | "where": [ 64 | "in the chest", 65 | "on the head", 66 | "on the bum" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /plugins/data/slaps.txt: -------------------------------------------------------------------------------- 1 | slaps {user} with a {item}. 2 | slaps {user} around a bit with a {item}. 3 | throws a {item} at {user}. 4 | chucks a few {item}s at {user}. 5 | grabs a {item} and throws it in {user}'s face. 6 | launches a {item} in {user}'s general direction. 7 | sits on {user}'s face while slamming a {item} into their crotch. 8 | starts slapping {user} silly with a {item}. 9 | holds {user} down and repeatedly whacks them with a {item}. 10 | prods {user} with a flaming {item}. 11 | picks up a {item} and whacks {user} with it. 12 | ties {user} to a chair and throws a {item} at them. 13 | hits {user} on the head with a {item}. 14 | ties {user} to a pole and whips them with a {item}. 15 | -------------------------------------------------------------------------------- /plugins/data/smileys.txt: -------------------------------------------------------------------------------- 1 | ¢‿¢ 2 | ©¿© o 3 | ª{•̃̾_•̃̾}ª 4 | ¬_¬ 5 | ¯\(º_o)/¯ 6 | ¯\(º o)/¯ 7 | ¯\_(⊙︿⊙)_/¯ 8 | ¯\_(ツ)_/¯ 9 | °ω° 10 | °Д° 11 | °‿‿° 12 | ¿ⓧ_ⓧﮌ 13 | Ò,ó 14 | ó‿ó 15 | ô⌐ô 16 | ôヮô 17 | ŎםŎ 18 | ŏﺡó 19 | ʕ•̫͡•ʔ 20 | ʕ•ᴥ•ʔ 21 | ʘ‿ʘ 22 | ˚•_•˚ 23 | ˚⌇˚ 24 | ˚▱˚ 25 | ̿ ̿̿'̿'\̵͇̿̿\=(•̪●)=/̵͇̿̿/'̿̿ ̿ ̿ ̿ 26 | Σ ◕ ◡ ◕ 27 | Σ(゚Д゚ ) 28 | Φ,Φ 29 | δﺡό 30 | σ_σ 31 | д_д 32 | ф_ф 33 | щ(゚Д゚щ) 34 | Ծ_Ծ 35 | ٩๏̯͡๏۶ 36 | ٩๏̯͡๏)۶ 37 | ٩◔̯◔۶ 38 | ٩(͡๏̯͡๏)۶ 39 | ٩(͡๏̯ ͡๏)۶ 40 | ٩(ಥ_ಥ)۶ 41 | ٩(•̮̮̃•̃)۶ 42 | ٩(●̮̮̃•̃)۶ 43 | ٩(●̮̮̃●̃)۶ 44 | ٩(。͡•‿•。)۶ 45 | ٩(-̮̮̃•̃)۶ 46 | ٩(-̮̮̃-̃)۶ 47 | ۞_۞ 48 | ۞_۟۞ 49 | ۹ↁﮌↁ 50 | ۹⌤_⌤۹ 51 | ॓_॔ 52 | १✌◡✌५ 53 | १|˚–˚|५ 54 | ਉ_ਉ 55 | ଘ_ଘ 56 | இ_இ 57 | ఠ_ఠ 58 | రృర 59 | ಠ¿ಠi 60 | ಠ‿ಠ 61 | ಠ⌣ಠ 62 | ಠ╭╮ಠ 63 | ಠ▃ಠ 64 | ಠ◡ಠ 65 | ಠ益ಠ 66 | ಠ益ಠ 67 | ಠ︵ಠ凸 68 | ಠ , ಥ 69 | ಠ.ಠ 70 | ಠoಠ 71 | ಠ_ృ 72 | ಠ_ಠ 73 | ಠ_๏ 74 | ಠ~ಠ 75 | ಡ_ಡ 76 | ತಎತ 77 | ತ_ತ 78 | ಥдಥ 79 | ಥ‿ಥ 80 | ಥ◡ಥ 81 | ಥ﹏ಥ 82 | ಥ_ಥ 83 | ಭ_ಭ 84 | ರ_ರ 85 | ಸ , ໖ 86 | ಸ_ಸ 87 | ക_ക 88 | อ้_อ้ 89 | อ_อ 90 | โ๏௰๏ใ ื 91 | ๏̯͡๏﴿ 92 | ๏̯͡๏ 93 | ๏̯͡๏﴿ 94 | ๏[-ิิ_•ิ]๏ 95 | ๏_๏ 96 | ໖_໖ 97 | ༺‿༻ 98 | ლ(´ڡ`ლ) 99 | ლ(◉◞౪◟◉‵ლ) 100 | ლ,ᔑ•ﺪ͟͠•ᔐ.ლ 101 | ᄽὁȍ ̪ őὀᄿ 102 | ᕙ(⇀‸↼‶)ᕗ 103 | •▱• 104 | •✞_✞• 105 | ‷̗ↂ凸ↂ‴̖ 106 | ‹•.•› 107 | ‹› ‹(•¿•)› ‹› 108 | ‹(ᵒᴥᵒ­­­­­)› 109 | ↁ_ↁ 110 | ⇎_⇎ 111 | ≧ヮ≦ 112 | ⊂•⊃_⊂•⊃ 113 | ⊂(◉‿◉)つ 114 | ⊙ω⊙ 115 | ⊙▂⊙ 116 | ⊙▃⊙ 117 | ⊙△⊙ 118 | ⊙︿⊙ 119 | ⊙﹏⊙ 120 | ⊙0⊙ 121 | ⊛ठ̯⊛ 122 | ⋋ō_ō` 123 | ━━━ヽ(ヽ(゚ヽ(゚∀ヽ(゚∀゚ヽ(゚∀゚)ノ゚∀゚)ノ∀゚)ノ゚)ノ)ノ━━━ 124 | ┌∩┐(◕_◕)┌∩┐ 125 | ┌( ಠ_ಠ)┘ 126 | ┌( ಥ_ಥ)┘ 127 | ╚(•⌂•)╝ 128 | ╭╮╭╮☜{•̃̾_•̃̾}☞╭╮╭╮ 129 | ╭✬⌢✬╮ 130 | ╯‵Д′)╯彡┻━┻ 131 | ╰☆╮ 132 | □_□ 133 | ►_◄ 134 | ◃┆◉◡◉┆▷ 135 | ◉△◉ 136 | ◉︵◉ 137 | ◉_◉ 138 | ○_○ 139 | ●¿●\ ~ 140 | ●_● 141 | ◔̯◔ 142 | ◔ᴗ◔ 143 | ◔ ⌣ ◔ 144 | ◔_◔ 145 | ◕ω◕ 146 | ◕‿◕ 147 | ◕◡◕ 148 | ◕ ◡ ◕ 149 | ◖♪_♪|◗ 150 | ◖|◔◡◉|◗ 151 | ◘_◘ 152 | ◙‿◙ 153 | ◜㍕◝ 154 | ◪_◪ 155 | ◮_◮ 156 | ☁ ☝ˆ~ˆ☂ 157 | ☆¸☆ 158 | ☉‿⊙ 159 | ☉_☉ 160 | ☐_☐ 161 | ☜(゚ヮ゚☜) 162 | ☜-(ΘLΘ)-☞ 163 | ☝☞✌ 164 | ☮▁▂▃▄☾ ♛ ◡ ♛ ☽▄▃▂▁☮ 165 | ☹_☹ 166 | ☻_☻ 167 | ☼.☼ 168 | ☾˙❀‿❀˙☽ 169 | ✌.ʕʘ‿ʘʔ.✌ 170 | ✌.|•͡˘‿•͡˘|.✌ 171 | ✖_✖ 172 | ❐‿❑ 173 | ⨀_⨀ 174 | ⨀_Ꙩ 175 | ⨂_⨂ 176 | 〆(・∀・@) 177 | 《〠_〠》 178 | 【•】_【•】 179 | 〠_〠 180 | 〴⋋_⋌〵 181 | のヮの 182 | ニガー? ━━━━━━(゚∀゚)━━━━━━ ニガー? 183 | ペ㍕˚\\ 184 | ヽ(´ー` )ノ 185 | ヽ(`Д´)ノ 186 | ヽ(o`皿′o)ノ 187 | ㅎ_ㅎ 188 | 乂◜◬◝乂 189 | 凸ಠ益ಠ)凸 190 | 句_句 191 | Ꙩ⌵Ꙩ 192 | Ꙩ_Ꙩ 193 | ꙩ_ꙩ 194 | Ꙫ_Ꙫ 195 | ꙫ_ꙫ 196 | ꙮ_ꙮ 197 | 흫_흫 198 | 句_句 199 | ﴾͡๏̯͡๏﴿ O'RLY? 200 | ¯\(ºдಠ)/¯ 201 | (·×·) 202 | (⌒Д⌒) 203 | (♯・∀・)⊃ 204 | (゜Д゜) 205 | (゚∀゚) 206 | ( ´☣///_ゝ///☣`) 207 | ( つ Д `) 208 | _☆( ´_⊃`)☆_ 209 | 。◕‿‿◕。 210 | 。◕ ‿ ◕。 211 | !⑈ˆ~ˆ!⑈ 212 | !(`・ω・。) 213 | (¬_¬) 214 | (°ℇ °) 215 | (°∀°) 216 | (´◉◞౪◟◉) 217 | (´・ω・`) 218 | (ʘ‿ʘ) 219 | (ʘ_ʘ) 220 | (˚இ˚) 221 | (͡๏̯͡๏) 222 | (ΘεΘ;) 223 | (Ծ‸ Ծ) 224 | (० ्०) 225 | (ு८ு_ .:) 226 | (ಠ‾ಠ) 227 | (ಠ‿ʘ) 228 | (ಠ‿ಠ) 229 | (ಠ⌣ಠ) 230 | (ಠ益ಠ ╬) 231 | (ಠ益ಠ) 232 | (ಠ_ృ) 233 | (ಠ_ಠ) 234 | (ಥ﹏ಥ) 235 | (ಥ_ಥ) 236 | (๏̯͡๏ ) 237 | (ᵔᴥᵔ) 238 | (•ω•) 239 | (•‿•) 240 | (• ε •) 241 | (≧ロ≦) 242 | (⌐■_■) 243 | (┛◉Д◉)┛┻━┻ 244 | (╬ಠ益ಠ) 245 | (╬◣д◢) 246 | (╬ ಠ益ಠ) 247 | (╯°□°)╯︵ ┻━┻ 248 | (▰˘◡˘▰) 249 | (●´ω`●) 250 | (◑◡◑) 251 | (◕‿◕) 252 | (◕︵◕) 253 | (◕ ^ ◕) 254 | (◕_◕) 255 | (◜௰◝) 256 | (◣_◢) 257 | (☞゚∀゚)☞ 258 | (☞゚ヮ゚)☞ 259 | (☞゚ ∀゚ )☞ 260 | (☼◡☼) 261 | (☼_☼) 262 | (✌゚∀゚)☞ 263 | ( ・∀・) 264 | ( ・ัω・ั)? 265 | ( ゚∀゚)o彡゜えーりんえーりん!! 266 | (づ。◕‿‿◕。)づ 267 | (ノಠ益ಠ)ノ彡┻━┻ 268 | (ノ ◑‿◑)ノ 269 | (;一_一) 270 | (。◕‿‿◕。) 271 | (。◕‿◕。) 272 | (。◕ ‿ ◕。) 273 | (。・ω..・)っ 274 | (ノ◕ヮ◕)ノ*:・゚✧ 275 | (゚∀゚) 276 | (゚ヮ゚) 277 | ( ̄ー ̄) 278 | ( °٢° ) 279 | ( •_•)>⌐■-■ 280 | ( ・ิз・ิ) 281 | (*..Д`) 282 | (*..д`*) 283 | (-’๏_๏’-) 284 | (/◔ ◡ ◔)/ 285 | (///_ಥ) 286 | (>'o')> ♥ <('o'<) 287 | \(◕ ◡ ◕\) 288 | ^̮^ 289 | ^ㅂ^ 290 | _(͡๏̯͡๏)_ 291 | {´◕ ◡ ◕`} 292 | {ಠ_ಠ}__,,|, 293 | {◕ ◡ ◕} 294 | -------------------------------------------------------------------------------- /plugins/data/troll.txt: -------------------------------------------------------------------------------- 1 | Butt Penis 2 | testing 3 | hue 4 | uwot 5 | -------------------------------------------------------------------------------- /plugins/debt.py: -------------------------------------------------------------------------------- 1 | # us debt plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | def parse(html): 8 | soup = BeautifulSoup(html, 'lxml') 9 | query = soup.find(id='debtDisplay') 10 | 11 | if query is None: 12 | return "unknown" 13 | else: 14 | return "$" + query.text 15 | 16 | 17 | @hook.command(autohelp=False) 18 | def debt(inp): 19 | """debt -- returns the us national debt""" 20 | 21 | url = "https://commodity.com/debt-clock/us/" 22 | html = request.get(url) 23 | debt = parse(html) 24 | 25 | return "Current US Debt: \x02{}\x02".format(debt) 26 | -------------------------------------------------------------------------------- /plugins/dice.py: -------------------------------------------------------------------------------- 1 | # Written by Scaevolus, updated by Lukeroge and ine 2 | 3 | import re 4 | import random 5 | from util import hook 6 | 7 | 8 | whitespace_re = re.compile(r'\s+') 9 | valid_diceroll = r'^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$' 10 | valid_diceroll_re = re.compile(valid_diceroll, re.I) 11 | sign_re = re.compile(r'[+-]?(?:\d*d)?(?:\d+|F)', re.I) 12 | split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) 13 | 14 | 15 | def nrolls(count, n): 16 | "roll an n-sided die count times" 17 | if n == "F": 18 | return [random.randint(-1, 1) for x in xrange(min(count, 100))] 19 | if n < 2: # it's a coin 20 | if count < 100: 21 | return [random.randint(0, 1) for x in xrange(count)] 22 | else: # fake it 23 | return [int(random.normalvariate(0.5 * count, (0.75 * count) ** 0.5))] 24 | else: 25 | if count < 100: 26 | return [random.randint(1, n) for x in xrange(count)] 27 | else: # fake it 28 | return [ 29 | int( 30 | random.normalvariate( 31 | 0.5 * (1 + n) * count, (((n + 1) * (2 * n + 1) / 6.0 - (0.5 * (1 + n)) ** 2) * count) ** 0.5 32 | ) 33 | ) 34 | ] 35 | 36 | 37 | @hook.command('roll') 38 | # @hook.regex(valid_diceroll, re.I) 39 | @hook.command 40 | def dice(inp): 41 | "dice <diceroll> -- Simulates dicerolls. Example: '2d20-d5+4' = roll 2 D20s, subtract 1D5, add 4" 42 | 43 | if "d" not in inp: 44 | return 'that doesnt look like a dice' 45 | 46 | validity = valid_diceroll_re.match(inp) 47 | 48 | if validity is None: 49 | return 'that isnt a dice' 50 | 51 | spec = whitespace_re.sub('', inp) 52 | if not valid_diceroll_re.match(spec): 53 | return "Invalid diceroll" 54 | 55 | groups = sign_re.findall(spec) 56 | total = 0 57 | rolls = [] 58 | 59 | for roll in groups: 60 | count, side = split_re.match(roll).groups() 61 | count = int(count) if count not in " +-" else 1 62 | if side.upper() == "F": # fudge dice are basically 1d3-2 63 | for fudge in nrolls(count, "F"): 64 | if fudge == 1: 65 | rolls.append("\x033+\x0F") 66 | elif fudge == -1: 67 | rolls.append("\x034-\x0F") 68 | else: 69 | rolls.append("0") 70 | total += fudge 71 | elif side == "": 72 | total += count 73 | else: 74 | side = int(side) 75 | if side > 10000000: 76 | return 'i cant make a dice with that many faces :(' 77 | try: 78 | if count > 0: 79 | dice = nrolls(count, side) 80 | rolls += map(str, dice) 81 | total += sum(dice) 82 | else: 83 | dice = nrolls(-count, side) 84 | rolls += [str(-x) for x in dice] 85 | total -= sum(dice) 86 | except OverflowError: 87 | return "Thanks for overflowing a float, jerk >:[" 88 | 89 | if len(rolls) == 1: 90 | return "Rolled {}".format(total) 91 | else: 92 | # show details for multiple dice 93 | return "Rolled {} [{}]".format(total, ", ".join(rolls)) 94 | -------------------------------------------------------------------------------- /plugins/dictionary.py: -------------------------------------------------------------------------------- 1 | # dictionary and etymology plugin by ine (2020) 2 | from util import hook 3 | from utilities import request, formatting 4 | from bs4 import BeautifulSoup 5 | 6 | dict_url = 'http://ninjawords.com/' 7 | eth_url = 'https://www.etymonline.com/word/' 8 | 9 | 10 | @hook.command('dictionary') 11 | @hook.command 12 | def define(inp): 13 | "define <word> -- Fetches definition of <word>." 14 | 15 | html = request.get(dict_url + request.urlencode(inp)) 16 | soup = BeautifulSoup(html, 'lxml') 17 | 18 | definitions = soup.find_all('dd') 19 | 20 | if len(definitions) == 0: 21 | return "Definition not found" 22 | 23 | output = 'Definition of "' + inp + '":' 24 | 25 | # used to number the many definitions 26 | i = 1 27 | 28 | for definition in definitions: 29 | if 'article' in definition['class']: 30 | text = formatting.compress_whitespace(definition.text.strip()) 31 | output = output + ' \x02' + text + '\x02' 32 | i = 1 33 | 34 | elif 'entry' in definition['class']: 35 | definition = definition.find('div', attrs={'class': 'definition'}) 36 | text = formatting.compress_whitespace(definition.text.strip()) 37 | output = output + text.replace(u'\xb0', ' \x02{}.\x02 '.format(i)) 38 | i = i + 1 39 | 40 | # theres 'synonyms' and 'examples' too 41 | 42 | # arbitrary length limit 43 | if len(output) > 360: 44 | output = output[:360] + '\x0f... More at https://en.wiktionary.org/wiki/' + inp 45 | 46 | return output 47 | 48 | 49 | @hook.command 50 | def etymology(inp): 51 | "etymology <word> -- Retrieves the etymology of <word>." 52 | 53 | html = request.get(eth_url + request.urlencode(inp)) 54 | soup = BeautifulSoup(html, 'lxml') 55 | # the page uses weird class names like "section.word__definatieon--81fc4ae" 56 | # if it breaks change the selector to [class~="word_"] 57 | results = soup.select('div[class^="word"] section[class^="word__def"] > p') 58 | 59 | if len(results) == 0: 60 | return 'No etymology found for ' + inp 61 | 62 | output = u'Ethymology of "' + inp + '":' 63 | i = 1 64 | 65 | for result in results: 66 | text = formatting.compress_whitespace(result.text.strip()) 67 | output = output + u' \x02{}.\x02 {}'.format(i, text) 68 | i = i + 1 69 | 70 | if len(output) > 400: 71 | output = output[:400] + '\x0f... More at https://www.etymonline.com/word/select' 72 | 73 | return output 74 | -------------------------------------------------------------------------------- /plugins/distance.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | from utilities import request 3 | from bs4 import BeautifulSoup 4 | 5 | 6 | def fetch(start, dest): 7 | start = request.urlencode(start) 8 | dest = request.urlencode(dest) 9 | url = "http://www.travelmath.com/flying-distance/from/{}/to/{}".format(start, dest) 10 | html = request.get(url) 11 | return html 12 | 13 | 14 | def parse(html): 15 | soup = BeautifulSoup(html, 'lxml') 16 | query = soup.find('h1', {'class': 'main'}) 17 | distance = soup.find('h3', {'class': 'space'}) 18 | 19 | if query: 20 | query = query.get_text().strip() 21 | 22 | if distance: 23 | distance = distance.get_text().strip() 24 | 25 | return query, distance 26 | 27 | 28 | @hook.command 29 | def distance(inp): 30 | "distance <start> to <end> -- Calculate the distance between 2 places." 31 | if 'from ' in inp: 32 | inp = inp.replace('from ', '') 33 | start = inp.split(" to ")[0].strip() 34 | dest = inp.split(" to ")[1].strip() 35 | 36 | html = fetch(start, dest) 37 | query, distance = parse(html) 38 | 39 | if not distance: 40 | return "Could not calculate the distance from {} to {}.".format(start, dest) 41 | 42 | result = u"Distance: {} {}".format(query, distance) 43 | return result 44 | -------------------------------------------------------------------------------- /plugins/distro.py: -------------------------------------------------------------------------------- 1 | # distrowatch ranking plugin by ine (2020) 2 | from util import hook 3 | from utilities import request, iterable 4 | from bs4 import BeautifulSoup 5 | from time import time 6 | 7 | # distrowatch updates around midnight 8 | # update cache every 2 hours 9 | cache = '' 10 | cache_stale = 2 * 60 * 60 11 | last_refresh = time() 12 | 13 | data_limit = 4 # how many distros per each dataset 14 | allowed_datasets = [ 15 | 'Last 12 months', # popularity ranking 16 | 'Last 1 month', 17 | 'Trending past 12 months', # trending list 18 | 'Trending past 1 month', 19 | ] 20 | 21 | 22 | def refresh_cache(): 23 | print '[+] refreshing distrowatch cache' 24 | output = '[DistroWatch]' 25 | 26 | def parse_table(data): 27 | global data_limit 28 | distro_names = [] 29 | 30 | for distro in iterable.limit(data_limit, data): 31 | distro_names.append(distro.text.strip()) 32 | 33 | return ', '.join(distro_names) 34 | 35 | # most popular distros in the last 12, 6 and 1 months 36 | html = request.get('https://distrowatch.com/dwres.php?resource=popularity') 37 | soup = BeautifulSoup(html, 'lxml') 38 | tables = soup.select('td.NewsText tr td table') 39 | 40 | for table in tables: 41 | header = table.find('th', attrs={'class': 'Invert'}) 42 | data = table.find_all('td', attrs={'class': 'phr2'}) 43 | 44 | # skip table if it doesn't have distro info 45 | if header is None or data is None: 46 | continue 47 | 48 | # skip this table if its not wanted 49 | header = header.text.strip() 50 | if header not in allowed_datasets: 51 | continue 52 | 53 | output = output + ' \x02Popular\x02 (' + header.replace('Last ', '') + '): ' 54 | output = output + parse_table(data) + '.' 55 | 56 | # trending distros in the past 12, 6 and 1 months 57 | html = request.get('https://distrowatch.com/dwres.php?resource=trending') 58 | soup = BeautifulSoup(html, 'lxml') 59 | tables = soup.select('table table table table.News') 60 | 61 | for table in tables: 62 | header = table.find('th', attrs={'class': 'Invert'}) 63 | data = table.parent.find_all('td', attrs={'class': 'phr2'}) 64 | 65 | if header is None or data is None: 66 | continue 67 | 68 | # skip this table if its not wanted 69 | header = header.text.strip() 70 | if header not in allowed_datasets: 71 | continue 72 | 73 | output = output + ' \x02Trending\x02 (' + header.replace('Trending ', '') + '): ' 74 | output = output + parse_table(data) + '.' 75 | 76 | global cache 77 | cache = output 78 | 79 | 80 | @hook.command 81 | def distro(inp): 82 | # update if time passed is more than cache_stale 83 | global last_refresh, cache_stale, cache 84 | now = time() 85 | if now - last_refresh > cache_stale: 86 | refresh_cache() 87 | last_refresh = now 88 | 89 | return cache 90 | 91 | 92 | refresh_cache() 93 | -------------------------------------------------------------------------------- /plugins/fmylife.py: -------------------------------------------------------------------------------- 1 | # fuck my life plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | 6 | cache = [] 7 | 8 | 9 | def refresh_cache(): 10 | print "[+] refreshing fmylife cache" 11 | html = request.get('https://www.fmylife.com/random') 12 | soup = BeautifulSoup(html, 'lxml') 13 | posts = soup.find_all('a', attrs={'class': 'article-link'}) 14 | 15 | for post in posts: 16 | id = post['href'].split('_')[1].split('.')[0] 17 | text = post.text.strip() 18 | cache.append((id, text)) 19 | 20 | 21 | @hook.command(autohelp=False) 22 | def fml(inp): 23 | "fml -- Gets a random quote from fmyfife.com." 24 | 25 | if len(cache) < 2: 26 | refresh_cache() 27 | 28 | id, text = cache.pop() 29 | return '(#{}) {}'.format(id, text) 30 | 31 | 32 | refresh_cache() 33 | -------------------------------------------------------------------------------- /plugins/furry.py: -------------------------------------------------------------------------------- 1 | # furry booru plugin by ararouge (2020) 2 | from util import hook 3 | from utilities import request 4 | import random 5 | 6 | cache = [] 7 | lastsearch = '' 8 | 9 | 10 | def refresh_cache(inp): 11 | print "[+] refreshing furry cache" 12 | 13 | global cache 14 | global lastsearch 15 | cache = [] 16 | search = inp 17 | 18 | # these are special search queries in the booru 19 | for word in ['explicit', 'safe', 'nsfw', 'sfw']: 20 | search = search.replace(word, 'rating:' + word) 21 | 22 | lastsearch = search 23 | 24 | if inp == '': 25 | postjson = request.get_json('http://e621.net/posts.json?limit=10') 26 | else: 27 | postjson = request.get_json('http://e621.net/posts.json?limit=10&tags={}'.format(request.urlencode(search))) 28 | posts = postjson["posts"] 29 | 30 | for i in range(len(posts)): 31 | post = posts[i] 32 | id = post["id"] 33 | score = post["score"]["total"] 34 | url = post["file"]["url"] 35 | rating = post["rating"] 36 | tags = ", ".join(post["tags"]["general"]) 37 | cache.append((id, score, url, rating, tags)) 38 | 39 | random.shuffle(cache) 40 | return 41 | 42 | 43 | @hook.command('e621', autohelp=False) 44 | @hook.command(autohelp=False) 45 | def furry(inp): 46 | global lastsearch 47 | global cache 48 | 49 | inp = inp.lower() 50 | if inp not in lastsearch or len(cache) < 2: 51 | refresh_cache(inp) 52 | 53 | lastsearch = inp 54 | 55 | if len(cache) == 0: 56 | return 'No Results' 57 | 58 | id, score, url, rating, tags = cache.pop() 59 | 60 | if rating == 'e': 61 | rating = "\x02\x034NSFW\x03\x02" 62 | elif rating == 'q': 63 | rating = "\x02Questionable\x02" 64 | elif rating == 's': 65 | rating = "\x02\x033Safe\x03\x02" 66 | 67 | return u'\x02[{}]\x02 Score: \x02{}\x02 - Rating: {} - {}'.format(id, score, rating, url) 68 | -------------------------------------------------------------------------------- /plugins/geoip.py: -------------------------------------------------------------------------------- 1 | # geoip plugin by ine (2020) 2 | import re 3 | from socket import gethostbyname 4 | from util import hook 5 | from utilities import request 6 | 7 | 8 | dumb_ip_re = r'(\d+\.\d+\.\d+\.\d+)' 9 | dumb_domain_re = r'([a-zA-Z0-9]+\.[a-zA-Z0-9]+)' 10 | 11 | 12 | @hook.command 13 | def geoip(inp): 14 | "geoip <host/ip> -- Gets the location of <host/ip>" 15 | 16 | if re.match(dumb_ip_re, inp): 17 | return parse_ip(inp) 18 | elif re.match(dumb_domain_re, inp): 19 | try: 20 | ip = gethostbyname(inp) 21 | return parse_ip(ip) 22 | except IOError: 23 | return "[IP] cant resolve that domain to ipv4" 24 | else: 25 | return "[IP] doesnt look like a valid ip or domain" 26 | 27 | 28 | def parse_ip(ip): 29 | ip = request.urlencode(ip) 30 | data = request.get_json('https://ipinfo.io/' + ip, headers={'Accept': 'application/json'}) 31 | 32 | if data.get('error') is not None: 33 | if data['error'].get('title') == 'Wrong ip': 34 | return '[IP] That IP is not valid' 35 | else: 36 | return '[IP] Some error ocurred' 37 | 38 | # example for 8.8.8.8 39 | loc = data.get('loc') # 37.40, -122.07 40 | city = data.get('city') # Mountain View 41 | country = data.get('country') # US 42 | region = data.get('region') # California 43 | hostname = data.get('hostname') # dns.google 44 | timezone = data.get('timezone') # unreliable 45 | ip = data.get('ip') # 8.8.8.8 46 | org = data.get('org') # Google LLC 47 | 48 | return u"[IP] {} - {}, {}, {}".format(org, city, region, country) 49 | -------------------------------------------------------------------------------- /plugins/google.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from util import hook, web 4 | from utilities import formatting, request 5 | 6 | API_URL = 'https://www.googleapis.com/customsearch/v1' 7 | 8 | 9 | @hook.command('search') 10 | @hook.command('g') 11 | @hook.command 12 | def google(inp, bot=None): 13 | """google <query> -- Returns first google search result for <query>.""" 14 | inp = request.urlencode(inp) 15 | 16 | url = API_URL + u'?key={}&cx={}&num=1&safe=off&q={}' 17 | cx = bot.config['api_keys']['googleimage'] 18 | search = '+'.join(inp.split()) 19 | key = bot.config['api_keys']['google'] 20 | result = request.get_json(url.format(key, cx, search.encode('utf-8')))['items'][0] 21 | 22 | title = result['title'] 23 | content = formatting.remove_newlines(result['snippet']) 24 | link = result['link'] 25 | 26 | try: 27 | return u'{} -- \x02{}\x02: "{}"'.format(web.isgd(link), title, content) 28 | except Exception: 29 | return u'{} -- \x02{}\x02: "{}"'.format(link, title, content) 30 | 31 | 32 | @hook.regex(r'^\>(.*\.(gif|jpe?g|png|tiff|bmp))$', re.I) 33 | @hook.command('gi') 34 | def image(inp, bot=None): 35 | """image <query> -- Returns the first Google Image result for <query>.""" 36 | if type(inp) is unicode: 37 | filetype = None 38 | else: 39 | inp, filetype = inp.string[1:].split('.') 40 | 41 | cx = bot.config['api_keys']['googleimage'] 42 | search = '+'.join(inp.split()) 43 | key = bot.config['api_keys']['google'] 44 | 45 | if filetype: 46 | url = API_URL + u'?key={}&cx={}&searchType=image&num=1&safe=off&q={}&fileType={}' 47 | result = request.get_json(url.format(key, cx, search.encode('utf-8'), 48 | filetype))['items'][0]['link'] 49 | else: 50 | url = API_URL + u'?key={}&cx={}&searchType=image&num=1&safe=off&q={}' 51 | result = request.get_json(url.format(key, cx, search.encode('utf-8')))['items'][0]['link'] 52 | 53 | try: 54 | return web.isgd(result) 55 | except Exception as e: 56 | print '[!] Error while shortening:', e 57 | return result 58 | -------------------------------------------------------------------------------- /plugins/kernel.py: -------------------------------------------------------------------------------- 1 | # linux kernel version plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | import re 5 | 6 | 7 | @hook.command(autohelp=False) 8 | def kernel(inp, reply=None): 9 | data = request.get("https://www.kernel.org/finger_banner") 10 | lines = data.split('\n') 11 | 12 | versions = [] 13 | old_versions = [] 14 | for line in lines: 15 | info = re.match(r'^The latest ([[a-z0-9 \-\.]+) version of the Linux kernel is:\s*(.*)$', line) 16 | if info is None: 17 | continue 18 | 19 | name = info.group(1) 20 | version = info.group(2) 21 | 22 | if 'longterm' in name: 23 | old_versions.append(version) 24 | else: 25 | versions.append(name + ': ' + version) 26 | 27 | output = 'Linux kernel versions: ' + '; '.join(versions) 28 | 29 | if len(old_versions) > 0: 30 | output = output + '. Old longterm versions: ' + ', '.join(old_versions) 31 | 32 | return output 33 | -------------------------------------------------------------------------------- /plugins/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | log.py: written by Scaevolus 2009 3 | """ 4 | 5 | import os 6 | import codecs 7 | import time 8 | import re 9 | 10 | from util import hook 11 | 12 | 13 | log_fds = {} # '%(net)s %(chan)s': (filename, fd) 14 | 15 | timestamp_format = '%H:%M:%S' 16 | 17 | formats = { 18 | 'PRIVMSG': '<%(nick)s> %(msg)s', 19 | 'PART': '-!- %(nick)s [%(user)s@%(host)s] has left %(chan)s', 20 | 'JOIN': '-!- %(nick)s [%(user)s@%(host)s] has joined %(param0)s', 21 | 'MODE': '-!- mode/%(chan)s [%(param_tail)s] by %(nick)s', 22 | 'KICK': '-!- %(param1)s was kicked from %(chan)s by %(nick)s [%(msg)s]', 23 | 'TOPIC': '-!- %(nick)s changed the topic of %(chan)s to: %(msg)s', 24 | 'QUIT': '-!- %(nick)s has quit [%(msg)s]', 25 | 'PING': '', 26 | 'NOTICE': '-%(nick)s- %(msg)s' 27 | } 28 | 29 | ctcp_formats = { 30 | 'ACTION': '* %(nick)s %(ctcpmsg)s', 31 | 'VERSION': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', 32 | 'PING': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', 33 | 'TIME': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', 34 | 'FINGER': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s' 35 | } 36 | 37 | irc_color_re = re.compile(r'(\x03(\d+,\d+|\d)|[\x0f\x02\x16\x1f])') 38 | 39 | 40 | def get_log_filename(dir, server, chan): 41 | return os.path.join(dir, 'log', gmtime('%Y'), server, chan, 42 | (gmtime('%%s.%m-%d.log') % chan).lower()) 43 | 44 | 45 | def gmtime(format): 46 | return time.strftime(format, time.gmtime()) 47 | 48 | 49 | def beautify(input): 50 | format = formats.get(input.command, '%(raw)s') 51 | args = dict(input) 52 | 53 | leng = len(args['paraml']) 54 | for n, p in enumerate(args['paraml']): 55 | args['param' + str(n)] = p 56 | args['param_' + str(abs(n - leng))] = p 57 | 58 | args['param_tail'] = ' '.join(args['paraml'][1:]) 59 | args['msg'] = irc_color_re.sub('', args['msg']) 60 | 61 | if input.command == 'PRIVMSG' and input.msg.count('\x01') >= 2: 62 | ctcp = input.msg.split('\x01', 2)[1].split(' ', 1) 63 | if len(ctcp) == 1: 64 | ctcp += [''] 65 | args['ctcpcmd'], args['ctcpmsg'] = ctcp 66 | format = ctcp_formats.get(args['ctcpcmd'], 67 | '%(nick)s [%(user)s@%(host)s] requested unknown CTCP ' 68 | '%(ctcpcmd)s from %(chan)s: %(ctcpmsg)s') 69 | 70 | return format % args 71 | 72 | 73 | def get_log_fd(dir, server, chan): 74 | fn = get_log_filename(dir, server, chan) 75 | cache_key = '%s %s' % (server, chan) 76 | filename, fd = log_fds.get(cache_key, ('', 0)) 77 | 78 | if fn != filename: # we need to open a file for writing 79 | if fd != 0: # is a valid fd 80 | fd.flush() 81 | fd.close() 82 | dir = os.path.split(fn)[0] 83 | if not os.path.exists(dir): 84 | os.makedirs(dir) 85 | fd = codecs.open(fn, 'a', 'utf-8') 86 | log_fds[cache_key] = (fn, fd) 87 | 88 | return fd 89 | 90 | 91 | @hook.singlethread 92 | @hook.event('*') 93 | def log(paraml, input=None, bot=None): 94 | timestamp = gmtime(timestamp_format) 95 | 96 | if bot is None or input is None: 97 | return 98 | 99 | fd = get_log_fd(bot.persist_dir, input.server, 'raw') 100 | fd.write(timestamp + ' ' + input.raw + '\n') 101 | 102 | if input.command == 'QUIT': # these are temporary fixes until proper 103 | input.chan = 'quit' # presence tracking is implemented 104 | if input.command == 'NICK': 105 | input.chan = 'nick' 106 | 107 | beau = beautify(input) 108 | 109 | if beau == '': # don't log this 110 | return 111 | 112 | if input.chan: 113 | fd = get_log_fd(bot.persist_dir, input.server, input.chan) 114 | fd.write(timestamp + ' ' + beau + '\n') 115 | 116 | print timestamp, input.chan, beau.encode('utf8', 'ignore') 117 | -------------------------------------------------------------------------------- /plugins/lyrics.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web 2 | import json 3 | import requests 4 | 5 | url = "http://www.genius.com/search?q={}" 6 | 7 | 8 | @hook.command 9 | def lyrics(inp, reply=None, bot=None): 10 | """lyrics <search> - Search genius.com for song lyrics""" 11 | 12 | base_url = "http://api.genius.com" 13 | headers = {'Authorization': bot.config['api_keys']['genius']} 14 | search_url = base_url + "/search" 15 | song_title = inp 16 | params = {'q': song_title} 17 | response = requests.get(search_url, params=params, headers=headers) 18 | return json.loads(response.text)['response']['hits'][0]['result']['url'] 19 | 20 | #inp = '+'.join(inp.split()) 21 | #soup = http.get_soup(url.format(inp)) 22 | #print soup 23 | #result = soup.findAll('a', {'class': 'mini_card'}) 24 | #print 'penis' 25 | #print result 26 | #reply(result[0]['href']) 27 | # if "pastelyrics" in inp: 28 | # dopaste = True 29 | # inp = inp.replace("pastelyrics", "").strip() 30 | # else: 31 | # dopaste = False 32 | # soup = http.get_soup(url + inp.replace(" ", "+")) 33 | # if "Try to compose less restrictive search query" in soup.find('div', {'id': 'inn'}).text: 34 | # return "No results. Check spelling." 35 | # div = None 36 | # for i in soup.findAll('div', {'class': 'sen'}): 37 | # if "/lyrics/" in i.find('a')['href']: 38 | # div = i 39 | # break 40 | # if div: 41 | # title = div.find('a').text 42 | # link = div.find('a')['href'] 43 | # if dopaste: 44 | # newsoup = http.get_soup(link) 45 | # try: 46 | # lyrics = newsoup.find('div', {'style': 'margin-left:10px;margin-right:10px;'}).text.strip() 47 | # pasteurl = " " + web.haste(lyrics) 48 | # except Exception as e: 49 | # pasteurl = " (\x02Unable to paste lyrics\x02 [{}])".format(str(e)) 50 | # else: 51 | # pasteurl = "" 52 | # artist = div.find('b').text.title() 53 | # lyricsum = div.find('div').text 54 | # if "\r\n" in lyricsum.strip(): 55 | # lyricsum = " / ".join(lyricsum.strip().split("\r\n")[0:4]) # truncate, format 56 | # else: 57 | # lyricsum = " / ".join(lyricsum.strip().split("\n")[0:4]) # truncate, format 58 | # return u"\x02{}\x02 by \x02{}\x02 {}{} - {}".format(title, artist, web.try_isgd(link), pasteurl, 59 | # lyricsum[:-3]) 60 | # else: 61 | # return "No song results. " + url + inp.replace(" ", "+") 62 | -------------------------------------------------------------------------------- /plugins/masshighlight.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from util import hook, database, user 4 | 5 | global userlist 6 | userlist = {} 7 | 8 | @hook.event('353') 9 | def onnames(input, conn=None, bot=None): 10 | global userlist 11 | inp = re.sub('[~&@+%,\.]', '', ' '.join(input)) 12 | chan,users = re.match(r'.*#(\S+)(.*)', inp.lower()).group(1, 2) 13 | try: userlist[chan] 14 | except: userlist[chan] = [] 15 | userlist[chan] = set(userlist[chan])|set(users.split(' ')) 16 | 17 | 18 | @hook.event("JOIN") 19 | def onjoined_addhighlight(inp,input=None, conn=None, chan=None,raw=None): 20 | global userlist 21 | try: userlist[input.chan.lower().replace('#','')].add(input.nick.lower()) 22 | except: return 23 | 24 | 25 | @hook.sieve 26 | def highlight_sieve(bot, input, func, kind, args): 27 | fn = re.match(r'^plugins.(.+).py$', func._filename) 28 | if fn.group(1) == 'seen' or \ 29 | fn.group(1) == 'tell' or\ 30 | fn.group(1) == 'ai' or \ 31 | fn.group(1) == 'core_ctcp': return input 32 | 33 | global userlist 34 | try: users = userlist[input.chan.lower().replace('#','')] 35 | except: return input 36 | inp = set(re.sub('[#~&@+%,\.]', '', input.msg.lower()).split(' ')) 37 | if len(users & inp) >= 5: 38 | globaladmin = user.is_globaladmin(input.mask, input.chan, bot) 39 | db = bot.get_db_connection(input.conn) 40 | channeladmin = user.is_channeladmin(input.mask, input.chan, db) 41 | if not globaladmin and not channeladmin: 42 | if len(users & inp) >= 7: 43 | input.conn.send(u"MODE {} +b *!*{}".format(input.chan, user.format_hostmask(input.mask))) 44 | input.conn.send(u"KICK {} {} :MASSHIGHLIGHTING FAGGOT GET #REKT".format(input.chan, input.nick)) 45 | return input 46 | 47 | 48 | 49 | 50 | @hook.command(autohelp=False,adminonly=True) 51 | def users(inp, nick=None,chan=None,notice=None): 52 | notice(' '.join(userlist[chan.replace('#','')])) 53 | notice('Users: {}'.format(len( userlist[chan.replace('#','')]))) 54 | 55 | 56 | @hook.command(autohelp=False,adminonly=True) 57 | def getusers(inp, conn=None,chan=None): 58 | if inp: chan = inp 59 | conn.send('NAMES {}'.format(chan)) 60 | 61 | 62 | ### dev ### 63 | # @hook.command(autohelp=False,adminonly=True) 64 | # def testcompare(inp, conn=None,chan=None, notice=None): 65 | # users = 'greenbagels ChanStat Pinyin austin j4jackj uguubot infinity fappyhour takoyaki unfinity themadman Knish Onee-chan'.split(' ') 66 | # inp = 'infinity uguubot themadman josh test2 test3'.split(' ') 67 | # a = set(users) 68 | # b = set(inp) 69 | # notice(' '.join(a|b)) 70 | -------------------------------------------------------------------------------- /plugins/mediawiki.py: -------------------------------------------------------------------------------- 1 | # mediawiki plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | 6 | API_QUERYPARAMS = "action=opensearch&format=json&limit=2&search=" 7 | OUTPUT_LIMIT = 240 # character limit 8 | INSTANCES = { 9 | 'encyclopediadramatica': { 10 | 'name': 'Encyclopedia Dramatica', 11 | 'search': 'https://encyclopediadramatica.wiki/api.php?' + API_QUERYPARAMS, 12 | 'regex': r'(https?://encyclopediadramatica\.wiki/index\.php/[^ ]+)' 13 | }, 14 | 'wikipedia_en': { 15 | 'name': 'Wikipedia', 16 | 'search': 'https://en.wikipedia.org/w/api.php?' + API_QUERYPARAMS, 17 | 'regex': r'(https?://en\.wikipedia\.org/wiki/[^ ]+)' 18 | }, 19 | } 20 | 21 | 22 | def search(instance, query): 23 | if instance not in INSTANCES: 24 | return 25 | 26 | wiki = INSTANCES[instance] 27 | search = request.get_json(wiki['search'] + request.urlencode(query)) 28 | 29 | titles = search[1] 30 | descriptions = search[2] 31 | urls = search[3] 32 | 33 | return (titles, descriptions, urls) 34 | 35 | 36 | def scrape_text(url): 37 | html = request.get(url) 38 | soup = BeautifulSoup(html, 'lxml') 39 | title = soup.find('h1', attrs={'id': 'firstHeading'}) 40 | body = soup.find('div', attrs={'id': 'mw-content-text'}) 41 | 42 | if title: 43 | title = title.text.strip() 44 | 45 | if body is None: 46 | return "Error reading the article" 47 | 48 | output = [] 49 | for paragraph in body.find_all('p'): 50 | text = paragraph.text.strip() 51 | if len(text) > 4: # skip empty paragraphs 52 | output.append(text) 53 | 54 | output = ' '.join(output) 55 | 56 | return output, title 57 | 58 | 59 | def command_wrapper(instance, inp): 60 | titles, descriptions, urls = search(instance, inp) 61 | 62 | if not titles: 63 | return "No results found." 64 | 65 | title = titles[0] 66 | url = urls[0] 67 | 68 | # `real_title` shows the article title after a 69 | # redirect. its generally longer than `title` 70 | output, real_title = scrape_text(url) 71 | 72 | if len(output) > OUTPUT_LIMIT: 73 | output = output[:OUTPUT_LIMIT] + '...' 74 | 75 | if title == real_title: 76 | return u'\x02{} -\x02 {} \x02-\x02 {}'.format(title, output, url) 77 | else: 78 | return u'\x02{} -\x02 {} \x02-\x02 {} (redirected from {})'.format(real_title, output, url, title) 79 | 80 | 81 | def url_wrapper(instance, url): 82 | output, title = scrape_text(url) 83 | 84 | if len(output) > OUTPUT_LIMIT: 85 | output = output[:OUTPUT_LIMIT] + '...' 86 | 87 | return u'\x02{} -\x02 {}'.format(title, output) 88 | 89 | 90 | @hook.regex(INSTANCES['encyclopediadramatica']['regex']) 91 | def drama_url(match): 92 | url = match.group(1) 93 | return url_wrapper('encyclopediadramatica', url) 94 | 95 | 96 | @hook.command('encyclopediadramatica') 97 | @hook.command 98 | def drama(inp): 99 | "drama <article> -- search an Encyclopedia Dramatica article" 100 | return command_wrapper('encyclopediadramatica', inp) 101 | 102 | 103 | @hook.regex(INSTANCES['wikipedia_en']['regex']) 104 | def wikipedia_url(match): 105 | url = match.group(1) 106 | return url_wrapper('wikipedia_en', url) 107 | 108 | 109 | @hook.command('wiki') 110 | @hook.command 111 | def wikipedia(inp): 112 | "wikipedia <article> -- search a wikipedia article" 113 | return command_wrapper('wikipedia_en', inp) 114 | -------------------------------------------------------------------------------- /plugins/namegen.py: -------------------------------------------------------------------------------- 1 | # Plugin by Lukeroge 2 | from util import hook 3 | from util.text import get_text_list 4 | import json, random, re, os 5 | 6 | TEMPLATE_RE = re.compile(r"\{(.+?)\}") 7 | GEN_DIR = "./plugins/data/name_files/" 8 | 9 | 10 | def get_generator(_json): 11 | data = json.loads(_json) 12 | return NameGenerator(data["name"], data["templates"], 13 | data["default_templates"], data["parts"]) 14 | 15 | 16 | class NameGenerator(object): 17 | def __init__(self, name, templates, default_templates, parts): 18 | self.name = name 19 | self.templates = templates 20 | self.default_templates = default_templates 21 | self.parts = parts 22 | 23 | def generate_name(self, template=None): 24 | """ 25 | Generates one name using the specified templates. 26 | If no templates are specified, use a random template from the default_templates list. 27 | """ 28 | name = self.templates[template or random.choice(self.default_templates)] 29 | 30 | # get a list of all name parts we need 31 | name_parts = TEMPLATE_RE.findall(name) 32 | 33 | for name_part in name_parts: 34 | part = random.choice(self.parts[name_part]) 35 | name = name.replace("{%s}" % name_part, part) 36 | 37 | return name 38 | 39 | def generate_names(self, amount, template=None): 40 | names = [] 41 | for i in xrange(amount): 42 | names.append(self.generate_name()) 43 | return names 44 | 45 | def get_template(self, template): 46 | return self.templates[template] 47 | 48 | 49 | @hook.command(autohelp=False) 50 | def namegen(inp, notice=None): 51 | "namegen [generator] -- Generates some names using the chosen generator. " \ 52 | "'namegen list' will display a list of all generators." 53 | 54 | # clean up the input 55 | inp = inp.strip().lower() 56 | 57 | # get a list of available name generators 58 | files = os.listdir(GEN_DIR) 59 | all_modules = [] 60 | for i in files: 61 | if os.path.splitext(i)[1] == ".json": 62 | all_modules.append(os.path.splitext(i)[0]) 63 | all_modules.sort() 64 | 65 | # command to return a list of all available generators 66 | if inp == "list": 67 | message = "Available generators: " 68 | message += get_text_list(all_modules, 'and') 69 | notice(message) 70 | return 71 | 72 | if inp: 73 | selected_module = inp.split()[0] 74 | else: 75 | # make some generic fantasy names 76 | selected_module = "fantasy" 77 | 78 | # check if the selected module is valid 79 | if not selected_module in all_modules: 80 | return "Invalid name generator :(" 81 | 82 | # load the name generator 83 | with open(os.path.join(GEN_DIR, "{}.json".format(selected_module))) as f: 84 | try: 85 | generator = get_generator(f.read()) 86 | except ValueError as error: 87 | return "Unable to read name file: {}".format(error) 88 | 89 | # time to generate some names 90 | name_list = generator.generate_names(10) 91 | 92 | # and finally return the final message :D 93 | return "Some names to ponder: {}.".format(get_text_list(name_list, 'and')) 94 | -------------------------------------------------------------------------------- /plugins/newegg.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, text, web 2 | import json 3 | import re 4 | 5 | ## CONSTANTS 6 | 7 | ITEM_URL = "http://www.newegg.com/Product/Product.aspx?Item={}" 8 | 9 | API_PRODUCT = "http://www.ows.newegg.com/Products.egg/{}/ProductDetails" 10 | API_SEARCH = "http://www.ows.newegg.com/Search.egg/Advanced" 11 | 12 | NEWEGG_RE = (r"(?:(?:www.newegg.com|newegg.com)/Product/Product\.aspx\?Item=)([-_a-zA-Z0-9]+)", re.I) 13 | 14 | 15 | ## OTHER FUNCTIONS 16 | 17 | def format_item(item, show_url=True): 18 | """ takes a newegg API item object and returns a description """ 19 | title = text.truncate_str(item["Title"], 50) 20 | 21 | # format the rating nicely if it exists 22 | if not item["ReviewSummary"]["TotalReviews"] == "[]": 23 | rating = "Rated {}/5 ({} ratings)".format(item["ReviewSummary"]["Rating"], 24 | item["ReviewSummary"]["TotalReviews"][1:-1]) 25 | else: 26 | rating = "No Ratings" 27 | 28 | if not item["FinalPrice"] == item["OriginalPrice"]: 29 | price = "{FinalPrice}, was {OriginalPrice}".format(**item) 30 | else: 31 | price = item["FinalPrice"] 32 | 33 | tags = [] 34 | 35 | if item["Instock"]: 36 | tags.append("\x02Stock Available\x02") 37 | else: 38 | tags.append("\x02Out Of Stock\x02") 39 | 40 | if item["FreeShippingFlag"]: 41 | tags.append("\x02Free Shipping\x02") 42 | 43 | if item["IsFeaturedItem"]: 44 | tags.append("\x02Featured\x02") 45 | 46 | if item["IsShellShockerItem"]: 47 | tags.append("\x02SHELL SHOCKER®\x02") 48 | 49 | # join all the tags together in a comma seperated string ("tag1, tag2, tag3") 50 | tag_text = u", ".join(tags) 51 | 52 | if show_url: 53 | # create the item URL and shorten it 54 | url = web.try_isgd(ITEM_URL.format(item["NeweggItemNumber"])) 55 | return u"\x02{}\x02 ({}) - {} - {} - {}".format(title, price, rating, 56 | tag_text, url) 57 | else: 58 | return u"\x02{}\x02 ({}) - {} - {}".format(title, price, rating, 59 | tag_text) 60 | 61 | 62 | ## HOOK FUNCTIONS 63 | 64 | @hook.regex(*NEWEGG_RE) 65 | def newegg_url(match): 66 | item_id = match.group(1) 67 | item = http.get_json(API_PRODUCT.format(item_id)) 68 | return format_item(item, show_url=False) 69 | 70 | 71 | @hook.command 72 | def newegg(inp): 73 | """newegg <item name> -- Searches newegg.com for <item name>""" 74 | 75 | # form the search request 76 | request = { 77 | "Keyword": inp, 78 | "Sort": "FEATURED" 79 | } 80 | 81 | # submit the search request 82 | r = http.get_json( 83 | 'http://www.ows.newegg.com/Search.egg/Advanced', 84 | post_data = json.dumps(request) 85 | ) 86 | 87 | # get the first result 88 | if r["ProductListItems"]: 89 | item = r["ProductListItems"][0] 90 | return format_item(item) 91 | else: 92 | return "No results found." 93 | 94 | 95 | -------------------------------------------------------------------------------- /plugins/nfl.py: -------------------------------------------------------------------------------- 1 | from util import formatting, hook, http, web 2 | import requests 3 | 4 | NFL_REALTIME_API = 'http://static.nfl.com/liveupdate/scores/scores.json' 5 | HOME = 'home' 6 | AWAY = 'away' 7 | ABBR = 'abbr' # e.g. NE, DAL 8 | SCORE = 'score' 9 | T = 'T' # Current score 10 | QTR = 'qtr' 11 | YL = 'yl' 12 | DOWN = 'down' 13 | TOGO = 'togo' 14 | CLOCK = 'clock' 15 | POSTEAM = 'posteam' # Possessing team 16 | 17 | 18 | def ordinaltg(n): 19 | """ Add an ordinal (-st, -nd, -rd) to a number 20 | """ 21 | return str(n) + {1: 'st', 2: 'nd', 3: 'rd'}.get(4 if 10 <= 22 | n % 100 < 20 else n % 10, "th") 23 | 24 | 25 | def get_match_info(home, away, match=None): 26 | """ Returns teams and scores for a given match 27 | """ 28 | 29 | home_abbr = home[ABBR] 30 | # If None type, turn it to 0 31 | home_score = home[SCORE][T] or 0 32 | away_abbr = away[ABBR] 33 | away_score = away[SCORE][T] or 0 34 | 35 | # Returning brief info for given match; used in getting all NFL games for 36 | # current week 37 | if not match: 38 | return "{} {} {} {}".format( 39 | home_abbr, home_score, away_abbr, away_score) 40 | 41 | # Get detailed game stats for a specific match 42 | 43 | # Game has not started or has ended 44 | quarter = match[QTR] 45 | if not quarter or quarter.lower() in ["final", "pregame", "final overtime"]: 46 | return "{} {} {} {} - {}".format(home_abbr, 47 | home_score, 48 | away_abbr, 49 | away_score, 50 | quarter) 51 | 52 | # Game is ongoing, fetch and return detailed info 53 | yard_line = match[YL] 54 | down = ordinaltg(match[DOWN]) 55 | to_go = match[TOGO] 56 | clock = match[CLOCK] 57 | pos_team = match[POSTEAM] 58 | # Example: TB 14 GB 10 - 2Q 11:02 - [GB] 1st & 10 @ GB 25 59 | return "{} {} {} {} - {}Q {} - [{}] {} & {} @ {}".format( 60 | home_abbr, 61 | home_score, 62 | away_abbr, 63 | away_score, 64 | quarter, 65 | clock, 66 | pos_team, 67 | down, 68 | to_go, 69 | yard_line) 70 | 71 | 72 | @hook.command(autohelp=False) 73 | def nfl(inp): 74 | """nfl | nfl <team abbreviation> -- Returns all matchups for current week, or only for a specified team's matchup 75 | """ 76 | 77 | # Get real time data 78 | try: 79 | data = requests.get(NFL_REALTIME_API).json() 80 | except Exception as e: 81 | return "Could not get NFL data" 82 | 83 | # Convert input to uppercase; NFL team abbreviations are in uppercase 84 | team_abbr = inp.upper() 85 | 86 | if team_abbr: 87 | # If user has specified a team, return the match with that team 88 | for game_id in data: 89 | match = data[game_id] 90 | home = data[game_id][HOME] 91 | away = data[game_id][AWAY] 92 | if team_abbr in [home[ABBR], away[ABBR]]: 93 | return get_match_info(home, away, match) 94 | 95 | # Non-existent football team or team is not playing this week 96 | return "{} not found".format(inp) 97 | 98 | # Build entire schedule 99 | schedule = [] 100 | for game_id in data: 101 | match = data[game_id] 102 | home = match[HOME] 103 | away = match[AWAY] 104 | 105 | # Add all matches 106 | match_info = get_match_info(home, away) 107 | schedule.append(match_info) 108 | 109 | # Return all matches occurring in current week 110 | return ', '.join(schedule) 111 | -------------------------------------------------------------------------------- /plugins/radio.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | from bs4 import BeautifulSoup 3 | from utilities import request 4 | 5 | # all icecast 2.4+ servers support the /status-json.xsl api 6 | radios = { 7 | 'r/a/dio': { 8 | 'name': 'R/a/dio', 9 | 'api': 'https://stream.r-a-d.io/status-json.xsl', 10 | 'homepage': 'https://r-a-d.io/', 11 | 'source': 'main.mp3' 12 | }, 13 | 14 | 'eden': { 15 | 'name': 'Eden of the west Public Radio', 16 | 'api': 'https://www.edenofthewest.com/radio/8000/status-json.xsl', 17 | 'homepage': 'https://www.edenofthewest.com/', 18 | 'source': 'radio.mp3' 19 | }, 20 | 21 | 'ducky': { 22 | 'name': 'just some radio', 23 | 'api': 'https://radio.wolowolo.com:8443/status-json.xsl', 24 | 'homepage': 'https://radio.wolowolo.com/ducky/', 25 | 'source': 'ducky' 26 | }, 27 | 28 | 'chiru': { 29 | 'name': 'chiru.no', 30 | 'api': 'https://chiru.no:8080/status-json.xsl', 31 | 'homepage': 'https://chiru.no/', 32 | 'source': 'stream.mp3', 33 | }, 34 | 'flippy': { 35 | 'name': 'flippy radio', 36 | 'api': 'https://radio.wolowolo.com:8443/status-json.xsl', 37 | 'homepage': 'https://radio.wolowolo.com/flippy', 38 | 'source': 'flippy' 39 | } 40 | } 41 | 42 | 43 | @hook.command 44 | def radio(id): 45 | if id not in radios: 46 | return "we dont support that radio. try one of the following: " + ", ".join(radios.keys()) 47 | 48 | radio = radios[id] 49 | 50 | try: 51 | data = request.get_json(radio['api']) 52 | except ValueError: 53 | return "the radio " + id + " has some server issues right now. try again later" 54 | 55 | sources = data.get('icestats', {}).get('source', False) 56 | 57 | if sources is False: 58 | return "the radio " + id + " is offline" 59 | 60 | def build_message(source): 61 | title = source.get('title', 'Untitled') 62 | listeners = source.get('listeners', 0) 63 | #genre = sourc.get('genre', 'unknown') 64 | return u'{} is playing \x02{}\x02 for {} listeners. listen: {}'.format(id, title, listeners, radio['homepage']) 65 | 66 | # the icecast api returns either one object (for one stream) 67 | # or a list of sources (for multiple streams available) 68 | if isinstance(sources, dict): 69 | if sources.get('listenurl', '').endswith(radio['source']): 70 | return build_message(sources) 71 | 72 | elif isinstance(sources, list): 73 | for source in sources: 74 | if source.get('listenurl', '').endswith(radio['source']): 75 | return build_message(source) 76 | 77 | # didn't find it 78 | return "the radio " + id + " is offline" 79 | 80 | 81 | @hook.command 82 | def aradio(inp): 83 | return radio('r/a/dio') 84 | 85 | 86 | # fallback because chiru.no's api sometimes returns broken json 87 | @hook.command(autohelp=False) 88 | @hook.command('mutantradio', autohelp=False) 89 | def muradio(inp, say=False): 90 | "radio [url]-- Returns current mutantradio song" 91 | url = 'https://chiru.no:8080/status.xsl' 92 | page = request.get_text(url) 93 | soup = BeautifulSoup(page, 'lxml') 94 | stats = soup.find_all('td', 'streamstats') 95 | # for i in stats: 96 | # print i 97 | # print stats[2], stats[4], stats[5] 98 | listeners = stats[2].text 99 | genre = stats[4].text 100 | # url = stats[5].text 101 | song = stats[6].text.encode('utf-8').strip() 102 | return u"[muradio] Playing: {}, Genre: {}, Listening: {}, URL: https://chiru.no/".format(song, genre, listeners) 103 | -------------------------------------------------------------------------------- /plugins/religion.py: -------------------------------------------------------------------------------- 1 | # bible/koran plugin by ine (2020) 2 | from util import hook 3 | from utilities import request, iterable 4 | from utilities.formatting import compress_whitespace 5 | from bs4 import BeautifulSoup 6 | 7 | 8 | @hook.command('god') 9 | @hook.command 10 | def bible(inp, bot=None): 11 | """bible <passage> -- gets <passage> from the Bible (ESV)""" 12 | 13 | API_KEY = bot.config['api_keys'].get('english_bible', None) 14 | 15 | if API_KEY is None: 16 | return 'Bible error: no API key configured' 17 | 18 | url = "https://api.esv.org/v3/passage/text/?q=" + request.urlencode(inp) 19 | json = request.get_json(url, headers={"Authorization": "Token " + API_KEY}) 20 | 21 | if 'detail' in json: 22 | return 'Bible error (lol): ' + json['detail'] 23 | 24 | if 'passages' in json and len(json['passages']) == 0: 25 | return '[Bible] Not found' 26 | 27 | output = '[Bible]' 28 | 29 | if 'canonical' in json: 30 | output = output + ' \x02' + json['canonical'] + '\x02:' 31 | 32 | if 'passages' in json: 33 | output = output + ' ' + compress_whitespace('. '.join(json['passages'])) 34 | 35 | if len(output) > 320: 36 | output = output[:320] + '...' 37 | 38 | return output 39 | 40 | 41 | @hook.command('allah') 42 | @hook.command 43 | def koran(inp): 44 | "koran <chapter.verse> -- gets <chapter.verse> from the Koran. it can also search any text." 45 | 46 | url = 'https://quod.lib.umich.edu/cgi/k/koran/koran-idx?type=simple&q1=' + request.urlencode(inp) 47 | html = request.get(url) 48 | soup = BeautifulSoup(html, 'lxml') 49 | query = soup.find_all('li') 50 | 51 | if not query or len(query) == 0: 52 | return 'No results for ' + inp 53 | 54 | output = '[Koran] ' 55 | lines = [] 56 | 57 | for li in iterable.limit(4, query): 58 | lines.append(compress_whitespace(li.text)) 59 | 60 | output = output + ' '.join(lines) 61 | 62 | if len(output) > 320: 63 | output = output[:320] + '...' 64 | 65 | return output 66 | -------------------------------------------------------------------------------- /plugins/reminder.py: -------------------------------------------------------------------------------- 1 | from util import hook, scheduler 2 | 3 | 4 | @hook.command('remind') 5 | @hook.command() 6 | def reminder(inp, nick=None, conn=None): 7 | """reminder <time sec/min/hour/day/month/year> <message> --- reminds you of <message>.""" 8 | inp = inp.replace('second', 'sec').replace('minute', 'min').replace('minutes', 'min').replace('hours', 'hour').replace('days', 'day').replace('weeks', 'week').replace('years', 'year').replace('seconds', 'second') 9 | timer = scheduler.check_for_timers(inp, 'reminder') 10 | inp = ' '.join(inp.split()[2:]) 11 | if timer > 0: 12 | scheduler.schedule(timer, 1, "NOTICE {} :{}".format(nick, inp), conn) 13 | -------------------------------------------------------------------------------- /plugins/soundcloud.py: -------------------------------------------------------------------------------- 1 | from util import hook, http, web, text 2 | from urllib import urlencode 3 | import re 4 | 5 | sc_re = (r'(.*:)//(www.)?(soundcloud.com)(.*)', re.I) 6 | api_url = "http://api.soundcloud.com" 7 | sndsc_re = (r'(.*:)//(www.)?(snd.sc)(.*)', re.I) 8 | 9 | 10 | def soundcloud(url, api_key): 11 | data = http.get_json(api_url + '/resolve.json?' + urlencode({'url': url, 'client_id': api_key})) 12 | 13 | desc = "" 14 | if data['description']: desc = u": {} ".format(text.truncate_str(data['description'], 50)) 15 | 16 | genre = "" 17 | if data['genre']: genre = u"- Genre: \x02{}\x02 ".format(data['genre']) 18 | 19 | duration = "" 20 | if data['duration']: 21 | tracklength = float(data['duration']) / 60000 22 | tracklength = re.match('(.*\...)', str(tracklength)).group(1) 23 | if tracklength: duration = u" {} mins -".format(tracklength) 24 | 25 | 26 | url = web.try_isgd(data['permalink_url']) 27 | 28 | return u"SoundCloud track: \x02{}\x02 by \x02{}\x02 {}{}-{} {} plays, {} downloads, {} comments - {}".format( 29 | data['title'], data['user']['username'], desc, genre, duration, data['playback_count'], data['download_count'], 30 | data['comment_count'], url) 31 | 32 | 33 | @hook.regex(*sc_re) 34 | def soundcloud_url(match, bot=None): 35 | api_key = bot.config.get("api_keys", {}).get("soundcloud") 36 | if not api_key: 37 | print "Error: no api key set" 38 | return None 39 | url = match.group(1).split(' ')[-1] + "//" + (match.group(2) if match.group(2) else "") + match.group(3) + \ 40 | match.group(4).split(' ')[0] 41 | return soundcloud(url, api_key) 42 | 43 | 44 | @hook.regex(*sndsc_re) 45 | def sndsc_url(match, bot=None): 46 | api_key = bot.config.get("api_keys", {}).get("soundcloud") 47 | if not api_key: 48 | print "Error: no api key set" 49 | return None 50 | url = match.group(1).split(' ')[-1] + "//" + (match.group(2) if match.group(2) else "") + match.group(3) + \ 51 | match.group(4).split(' ')[0] 52 | return soundcloud(http.open(url).url, api_key) 53 | -------------------------------------------------------------------------------- /plugins/spellcheck.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | from enchant.checker import SpellChecker 3 | 4 | import enchant 5 | 6 | locale = "en_US" 7 | 8 | 9 | @hook.command 10 | def spell(inp): 11 | """spell <word/sentence> -- Check spelling of a word or sentence.""" 12 | 13 | if not enchant.dict_exists(locale): 14 | return "Could not find dictionary: {}".format(locale) 15 | 16 | if len(inp.split(" ")) > 1: 17 | # input is a sentence 18 | chkr = SpellChecker(locale) 19 | chkr.set_text(inp) 20 | 21 | offset = 0 22 | for err in chkr: 23 | # find the location of the incorrect word 24 | start = err.wordpos + offset 25 | finish = start + len(err.word) 26 | # get some suggestions for it 27 | suggestions = err.suggest() 28 | s_string = '/'.join(suggestions[:3]) 29 | s_string = "\x02{}\x02".format(s_string) 30 | # calculate the offset for the next word 31 | offset = (offset + len(s_string)) - len(err.word) 32 | # replace the word with the suggestions 33 | inp = inp[:start] + s_string + inp[finish:] 34 | return inp 35 | else: 36 | # input is a word 37 | dictionary = enchant.Dict(locale) 38 | is_correct = dictionary.check(inp) 39 | suggestions = dictionary.suggest(inp) 40 | s_string = ', '.join(suggestions[:10]) 41 | if is_correct: 42 | return '"{}" appears to be \x02valid\x02! ' \ 43 | '(suggestions: {})'.format(inp, s_string) 44 | else: 45 | return '"{}" appears to be \x02invalid\x02! ' \ 46 | '(suggestions: {})'.format(inp, s_string) 47 | -------------------------------------------------------------------------------- /plugins/stock.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import requests 3 | import json 4 | 5 | 6 | def color(change): 7 | if change < 0: 8 | # Orange 9 | return "05" 10 | else: 11 | # Green 12 | return "03" 13 | 14 | 15 | @hook.command 16 | def stock(inp, bot=None): 17 | """stock <symbol> -- gets stock information""" 18 | symbols = inp.upper() 19 | base_url = "https://cloud.iexapis.com/v1" 20 | token = bot.config["api_keys"]["iex"] 21 | params = {"token": token, "symbols": symbols, "types": "quote,stats"} 22 | 23 | try: 24 | data = requests.get(base_url + "/stock/market/batch", params=params) 25 | data = data.json() 26 | 27 | # https://iexcloud.io/docs/api/#quote 28 | quote_data = data[symbols]["quote"] 29 | 30 | current_price = quote_data["latestPrice"] 31 | symbol = quote_data["symbol"] 32 | day_high_price = quote_data["high"] 33 | day_low_price = quote_data["low"] 34 | day_change_percent = float("{:.2f}".format(quote_data["changePercent"] * 100)) 35 | day_change_color = color(day_change_percent) 36 | day_change_percent = "\x03{}{}%\x03".format( 37 | day_change_color, day_change_percent 38 | ) 39 | 40 | # https://iexcloud.io/docs/api/#key-stats 41 | stats_data = data[symbols]["stats"] 42 | name = stats_data["companyName"] 43 | 44 | year1_change_percent = float( 45 | "{:.2f}".format(stats_data["year1ChangePercent"] * 100) 46 | ) 47 | year1_change_color = color(float(year1_change_percent)) 48 | year1_change_percent = "\x03{}{}%\x03".format( 49 | year1_change_color, year1_change_percent 50 | ) 51 | 52 | month_6_change_percent = float( 53 | "{:.2f}".format(stats_data["month6ChangePercent"] * 100) 54 | ) 55 | month_6_change_color = color(float(month_6_change_percent)) 56 | month_6_change_percent = "\x03{}{}%\x03".format( 57 | month_6_change_color, month_6_change_percent 58 | ) 59 | 60 | day_30_change_percent = float( 61 | "{:.2f}".format(stats_data["day30ChangePercent"] * 100) 62 | ) 63 | day_30_change_color = color(float(day_30_change_percent)) 64 | day_30_change_percent = "\x03{}{}%\x03".format( 65 | day_30_change_color, day_30_change_percent 66 | ) 67 | 68 | day_5_change_percent = float( 69 | "{:.2f}".format(stats_data["day5ChangePercent"] * 100) 70 | ) 71 | day_5_change_color = color(float(day_5_change_percent)) 72 | day_5_change_percent = "\x03{}{}%\x03".format( 73 | day_5_change_color, day_5_change_percent 74 | ) 75 | 76 | if day_high_price: 77 | response = "\x02{} ({})\x02, Current: ${}, High: ${}, Low: ${}, 24h: {}, 5d: {}, 30d: {}, 6m: {}, 1y: {}".format( 78 | name, 79 | symbol, 80 | current_price, 81 | day_high_price, 82 | day_low_price, 83 | day_change_percent, 84 | day_5_change_percent, 85 | day_30_change_percent, 86 | month_6_change_percent, 87 | year1_change_percent, 88 | ) 89 | # Not in trading hours 90 | else: 91 | response = "\x02{} ({})\x02, Current: ${}, 24h: {}, 5d: {}, 30d: {}, 6m: {}, 1y: {}".format( 92 | name, 93 | symbol, 94 | current_price, 95 | day_change_percent, 96 | day_5_change_percent, 97 | day_30_change_percent, 98 | month_6_change_percent, 99 | year1_change_percent, 100 | ) 101 | except Exception as e: 102 | return "Could not get stock information for {}".format(symbols) 103 | 104 | return "[Stock] " + response 105 | -------------------------------------------------------------------------------- /plugins/system.py: -------------------------------------------------------------------------------- 1 | import os 2 | import psutil 3 | import re 4 | import time 5 | import platform 6 | from util import hook 7 | from datetime import timedelta 8 | 9 | 10 | def convert_kilobytes(kilobytes): 11 | if kilobytes >= 1024: 12 | megabytes = kilobytes / 1024 13 | size = '%.2f MB' % megabytes 14 | else: 15 | size = '%.2f KB' % kilobytes 16 | return size 17 | 18 | 19 | @hook.command(autohelp=False, adminonly=True) 20 | def system(inp): 21 | """system -- Retrieves information about the host system.""" 22 | hostname = platform.node() 23 | os = platform.platform() 24 | python_imp = platform.python_implementation() 25 | python_ver = platform.python_version() 26 | architecture = '-'.join(platform.architecture()) 27 | cpu = platform.machine() 28 | with open('/proc/uptime', 'r') as f: 29 | uptime_seconds = float(f.readline().split()[0]) 30 | uptime = str(timedelta(seconds = uptime_seconds)) 31 | 32 | return "Hostname: \x02{}\x02, Operating System: \x02{}\x02, Python " \ 33 | "Version: \x02{} {}\x02, Architecture: \x02{}\x02, CPU: \x02{}" \ 34 | "\x02, Uptime: \x02{}\x02".format(hostname, os, python_imp, python_ver, architecture, cpu, uptime) 35 | 36 | 37 | @hook.command(autohelp=False, adminonly=True) 38 | def memory(inp, notice=None): 39 | """memory -- Displays the bot's current memory usage.""" 40 | p = psutil.Process() 41 | mem = p.memory_info() 42 | rss, vms, heap = mem.rss/1000000, mem.vms/1000000, mem.data/1000000 43 | notice('%s %s %s' % (rss, vms, heap)) 44 | if os.name == "posix": 45 | # get process info 46 | status_file = open('/proc/self/status').read() 47 | s = dict(re.findall(r'^(\w+):\s*(.*)\s*$', status_file, re.M)) 48 | # get the data we need and process it 49 | data = s['VmRSS'], s['VmSize'], s['VmPeak'], s['VmStk'], s['VmData'] 50 | data = [float(i.replace(' kB', '')) for i in data] 51 | strings = [convert_kilobytes(i) for i in data] 52 | # prepare the output 53 | out = "Threads: \x02{}\x02, Real Memory: \x02{}\x02, Allocated Memory: \x02{}\x02, Peak " \ 54 | "Allocated Memory: \x02{}\x02, Stack Size: \x02{}\x02, Heap " \ 55 | "Size: \x02{}\x02".format(s['Threads'], strings[0], strings[1], strings[2], 56 | strings[3], strings[4]) 57 | # return output 58 | return out 59 | 60 | elif os.name == "nt": 61 | cmd = 'tasklist /FI "PID eq %s" /FO CSV /NH' % os.getpid() 62 | out = os.popen(cmd).read() 63 | memory = 0 64 | for amount in re.findall(r'([,0-9]+) K', out): 65 | memory += float(amount.replace(',', '')) 66 | memory = convert_kilobytes(memory) 67 | return "Memory Usage: \x02{}\x02".format(memory) 68 | 69 | else: 70 | return "Sorry, this command is not supported on your OS." 71 | 72 | 73 | @hook.command(autohelp=False) 74 | def uptime(inp, bot=None): 75 | """uptime -- Shows the bot's uptime.""" 76 | uptime_raw = round(time.time() - bot.start_time) 77 | uptime = timedelta(seconds=uptime_raw) 78 | with open('/proc/uptime', 'r') as f: 79 | sysuptime_seconds = float(f.readline().split()[0]) 80 | sysuptime = str(timedelta(seconds = sysuptime_seconds)) 81 | 82 | return "Uptime: \x02{}\x02, System Uptime: \x02{}\x02".format(uptime, sysuptime) 83 | 84 | 85 | @hook.command(autohelp=False, adminonly=True) 86 | def pid(inp): 87 | """pid -- Prints the bot's PID.""" 88 | return "PID: \x02{}\x02".format(os.getpid()) 89 | 90 | 91 | @hook.command(autohelp=False) 92 | def bots(inp): 93 | return "Reporting in! [Python] See http://uguubot.com" 94 | 95 | 96 | @hook.command(autohelp=False) 97 | def source(inp): 98 | return "\x02Taigabot\x02 - Fuck my shit up nigga https://github.com/FrozenPigs/Taigabot" 99 | 100 | -------------------------------------------------------------------------------- /plugins/times.py: -------------------------------------------------------------------------------- 1 | # ScottSteiner 2014 2 | from util import hook 3 | from datetime import datetime 4 | from pytz import timezone 5 | 6 | @hook.command(autohelp=False) 7 | def times(inp, bot=None): 8 | "times -- Shows times around the world." 9 | 10 | default_format = "%I:%M %p %Z" 11 | default_separator = " | " 12 | default_timezones = [ 13 | ("Los Angeles", "America/Los_Angeles"), 14 | ("New York", "America/New_York"), 15 | ("London", "Europe/London"), 16 | ("Berlin", "Europe/Berlin"), 17 | ("Kiev", "Europe/Kiev"), 18 | ("Tokyo", "Asia/Tokyo") 19 | ] 20 | 21 | out = [] 22 | utc = datetime.now(timezone('UTC')) 23 | 24 | tz_zones = bot.config["plugins"]["times"].get("time_zones", default_timezones) 25 | tz_format = bot.config["plugins"]["times"].get("format", default_format) 26 | tz_separator = bot.config["plugins"]["times"].get("separator", default_separator) 27 | 28 | for (location, tztext) in tz_zones: 29 | tzout = utc.astimezone(timezone(tztext)).strftime(tz_format) 30 | out.append("{} {}".format(location, tzout)) 31 | 32 | return tz_separator.join(out) 33 | -------------------------------------------------------------------------------- /plugins/urbandict.py: -------------------------------------------------------------------------------- 1 | # urban dictionary plugin by ine (2020) 2 | from util import hook 3 | from utilities import request, formatting 4 | 5 | base_url = 'https://api.urbandictionary.com/v0/define?term=' 6 | 7 | 8 | def clean_text(text): 9 | return formatting.compress_whitespace(text.replace('[', '').replace(']', '')) 10 | 11 | 12 | def search(input): 13 | json = request.get_json(base_url + request.urlencode(input)) 14 | 15 | if json is None or "error" in json or "errors" in json: 16 | return ["the server fucked up"] 17 | 18 | data = [] 19 | for item in json['list']: 20 | definition = item['definition'] 21 | word = item['word'] 22 | example = item['example'] 23 | votes_up = item['thumbs_up'] 24 | votes_down = item['thumbs_down'] 25 | 26 | output = '\x02' + word + '\x02 ' 27 | 28 | try: 29 | votes = int(votes_up) - int(votes_down) 30 | if votes > 0: 31 | votes = '+' + str(votes) 32 | except: 33 | votes = 0 34 | 35 | if votes != 0: 36 | output = output + '(' + str(votes) + ') ' 37 | 38 | output = output + clean_text(definition) 39 | 40 | if example: 41 | output = output + ' \x02Example:\x02 ' + clean_text(example) 42 | 43 | data.append(output) 44 | 45 | return data 46 | 47 | 48 | @hook.command('u') 49 | @hook.command('ud') 50 | @hook.command('nig') 51 | @hook.command('ebonics') 52 | @hook.command 53 | def urban(inp): 54 | "urban <phrase> -- Looks up <phrase> on urbandictionary.com." 55 | 56 | inp_val = inp.strip() 57 | inp_count = 1 58 | 59 | try: 60 | inp_rev = inp_val[::-1] 61 | inp_count, rest = inp_rev.split(' ', 1) 62 | 63 | inp_val = rest[::-1] 64 | inp_count = int(inp_count[::-1]) 65 | except: 66 | inp_val = inp.strip() 67 | inp_count = 1 68 | 69 | if (inp_count - 1) < 0: 70 | return '[ud] Indexing of results starts at 1' 71 | 72 | results = search(inp_val) 73 | 74 | # always return just the first one 75 | try: 76 | return '[ud %s/%s] %s' % ( 77 | inp_count, len(results), results[inp_count-1] 78 | ) 79 | except IndexError: 80 | return '[ud] Not found' 81 | -------------------------------------------------------------------------------- /plugins/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrozenPigs/Taigabot/2e61213c0793ec11c5b4b97c260f977e8e35c640/plugins/util/__init__.py -------------------------------------------------------------------------------- /plugins/util/database.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import log 3 | from sqlite3 import OperationalError 4 | 5 | channel_columns = ['chan NOT NULL', 6 | 'admins', 'permissions', 'ops', 'bans', 'disabled', 'ignored', 'badwords', 'flood', 'cmdflood', 'trimlength', 'autoop', 'votekick', 'voteban', 7 | 'primary key(chan)'] 8 | user_columns = ['nick NOT NULL', 9 | 'mask', 'version', 'location', 'lastfm', 'fines', 'battlestation', 'desktop', 'horoscope', 'greeting', 'waifu', 'husbando', 'birthday', 'homescreen', 'snapchat', 'mal', 'selfie', 'fit', 'handwriting', 'steam', 10 | 'primary key(nick)'] 11 | location_columns = ['location NOT NULL', 'latlong', 'address', 12 | 'primary key(location)'] 13 | 14 | db_ready = False 15 | 16 | def init(db): 17 | """Init the databases.""" 18 | global db_ready 19 | if not db_ready: 20 | db.execute('create table if not exists channels({});'. 21 | format(', '.join(channel_columns))) 22 | db.execute('create table if not exists users({});'. 23 | format(', '.join(user_columns))) 24 | db.execute('create table if not exists location({});'. 25 | format(', '.join(location_columns))) 26 | db.commit() 27 | db_ready = True 28 | 29 | 30 | def update(db): 31 | """Update the database columns.""" 32 | for i in channel_columns[1:-1]: 33 | try: 34 | db.execute('alter table channels add column {}'. 35 | format(i)) 36 | except OperationalError: 37 | pass 38 | for i in user_columns[1:-1]: 39 | try: 40 | db.execute('alter table users add column {}'. 41 | format(i)) 42 | except OperationalError: 43 | pass 44 | for i in location_columns[1:-1]: 45 | try: 46 | db.execute('alter table location add column {}'. 47 | format(i)) 48 | except OperationalError: 49 | pass 50 | 51 | def field_exists(db,table,matchfield,matchvalue): 52 | init(db) 53 | exists = db.execute("SELECT EXISTS(SELECT 1 FROM {} WHERE {}='{}' LIMIT 1);".format(table,matchfield,matchvalue.encode('utf8'))).fetchone()[0] 54 | if exists: return True 55 | else: return False 56 | 57 | def get(db,table,field,matchfield,matchvalue): 58 | init(db) 59 | try: 60 | matchvalue = matchvalue.encode('utf-8').lower() 61 | except: 62 | pass 63 | try: 64 | result = db.execute("SELECT {} FROM {} WHERE {}='{}';".format(field,table,matchfield,matchvalue)).fetchone() 65 | if result: return result[0].encode('utf-8') 66 | else: return False 67 | except: 68 | log.log("***ERROR: SELECT {} FROM {} WHERE {}='{}';".format(field,table,matchfield,matchvalue)) 69 | 70 | 71 | def set(db, table, field, value, matchfield, matchvalue): 72 | init(db) 73 | if value is None: value = '' 74 | try: 75 | matchvalue = matchvalue.encode('utf-8').lower() 76 | except: 77 | pass 78 | if type(value) is str: value = value.replace("'","").replace('\"', "") 79 | try: 80 | db.execute("ALTER TABLE {} ADD COLUMN {};".format(table, field)) 81 | except: 82 | pass 83 | 84 | try: 85 | if field_exists(db,table,matchfield,matchvalue): 86 | db.execute("UPDATE {} SET {} = '{}' WHERE {} = '{}';".format(table,field,value,matchfield,matchvalue)) 87 | else: 88 | db.execute("INSERT INTO {} ({},{}) VALUES ('{}','{}');".format(table,field,matchfield,value,matchvalue)) 89 | except: 90 | db.execute('UPDATE {} SET {} = "{}" WHERE {} = "{}";'.format(table,field,value,matchfield,matchvalue)) 91 | 92 | db.commit() 93 | return 94 | -------------------------------------------------------------------------------- /plugins/util/execute.py: -------------------------------------------------------------------------------- 1 | import http, web 2 | 3 | 4 | def eval_py(code, paste_multiline=True): 5 | attempts = 0 6 | 7 | while True: 8 | try: 9 | output = http.get("http://eval.appspot.com/eval", statement=code).rstrip('\n') 10 | # sometimes the API returns a blank string on first attempt, lets try again 11 | # and make sure it is actually supposed to be a blank string. ._. 12 | if output == "": 13 | output = http.get("http://eval.appspot.com/eval", statement=code).rstrip('\n') 14 | break 15 | except http.HTTPError: 16 | if attempts > 2: 17 | return "Failed to execute code." 18 | else: 19 | attempts += 1 20 | continue 21 | 22 | if "Traceback (most recent call last):" in output: 23 | status = "Python error: " 24 | else: 25 | status = "Code executed sucessfully: " 26 | 27 | if "\n" in output and paste_multiline: 28 | return status + web.haste(output) 29 | else: 30 | return output 31 | -------------------------------------------------------------------------------- /plugins/util/formatting.py: -------------------------------------------------------------------------------- 1 | def filesize(num, suffix='B'): 2 | for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: 3 | if abs(num) < 1024.0: 4 | return '{0:.2f}{1}{2}'.format(num, unit, suffix) 5 | num /= 1024.0 6 | return '{0:.1f}{}{}'.format(num, 'Yi', suffix) 7 | 8 | 9 | def output(scriptname, fields=[], color=11): 10 | return '[{}] {}'.format(scriptname, ' = '.join(fields)) 11 | -------------------------------------------------------------------------------- /plugins/util/hook.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import re 3 | 4 | 5 | def _hook_add(func, add, name=''): 6 | if not hasattr(func, '_hook'): 7 | func._hook = [] 8 | func._hook.append(add) 9 | 10 | if not hasattr(func, '_filename'): 11 | func._filename = func.func_code.co_filename 12 | 13 | if not hasattr(func, '_args'): 14 | argspec = inspect.getargspec(func) 15 | if name: 16 | n_args = len(argspec.args) 17 | if argspec.defaults: 18 | n_args -= len(argspec.defaults) 19 | if argspec.keywords: 20 | n_args -= 1 21 | if argspec.varargs: 22 | n_args -= 1 23 | if n_args != 1: 24 | pass 25 | #err = '%ss must take 1 non-keyword argument (%s)' % (name, 26 | # func.__name__) 27 | #raise ValueError(err) 28 | 29 | args = [] 30 | if argspec.defaults: 31 | end = bool(argspec.keywords) + bool(argspec.varargs) 32 | args.extend(argspec.args[-len(argspec.defaults): 33 | end if end else None]) 34 | if argspec.keywords: 35 | args.append(0) # means kwargs present 36 | func._args = args 37 | 38 | if not hasattr(func, '_thread'): # does function run in its own thread? 39 | func._thread = False 40 | 41 | 42 | def sieve(func): 43 | if func.func_code.co_argcount != 5: 44 | raise ValueError( 45 | 'sieves must take 5 arguments: (bot, input, func, type, args)') 46 | _hook_add(func, ['sieve', (func,)]) 47 | return func 48 | 49 | 50 | def command(arg=None, **kwargs): 51 | args = {} 52 | 53 | def command_wrapper(func): 54 | args.setdefault('name', func.func_name) 55 | _hook_add(func, ['command', (func, args)], 'command') 56 | return func 57 | 58 | if kwargs or not inspect.isfunction(arg): 59 | if arg is not None: 60 | args['name'] = arg 61 | args.update(kwargs) 62 | return command_wrapper 63 | else: 64 | return command_wrapper(arg) 65 | 66 | 67 | def event(arg=None, **kwargs): 68 | args = kwargs 69 | 70 | def event_wrapper(func): 71 | args['name'] = func.func_name 72 | args.setdefault('events', ['*']) 73 | _hook_add(func, ['event', (func, args)], 'event') 74 | return func 75 | 76 | if inspect.isfunction(arg): 77 | return event_wrapper(arg, kwargs) 78 | else: 79 | if arg is not None: 80 | args['events'] = arg.split() 81 | return event_wrapper 82 | 83 | 84 | def singlethread(func): 85 | func._thread = True 86 | return func 87 | 88 | 89 | def regex(regex, flags=0, **kwargs): 90 | args = kwargs 91 | 92 | def regex_wrapper(func): 93 | args['name'] = func.func_name 94 | args['regex'] = regex 95 | args['re'] = re.compile(regex, flags) 96 | _hook_add(func, ['regex', (func, args)], 'regex') 97 | return func 98 | 99 | if inspect.isfunction(regex): 100 | raise ValueError("regex decorators require a regex to match against") 101 | else: 102 | return regex_wrapper 103 | 104 | 105 | def on_start(param=None, **kwargs): 106 | """External on_start decorator. Can be used directly as a decorator, or with args to return a decorator 107 | :type param: function | None 108 | """ 109 | 110 | def _on_start_hook(func): 111 | args = kwargs 112 | _hook_add(func, ['on_start', (func, args)], 'on_start') 113 | return func 114 | 115 | if callable(param): 116 | return _on_start_hook(param) 117 | else: 118 | return lambda func: _on_start_hook(func) 119 | 120 | 121 | # this is temporary, to ease transition 122 | onload = on_start 123 | -------------------------------------------------------------------------------- /plugins/util/scheduler.py: -------------------------------------------------------------------------------- 1 | from util import hook 2 | import sched, time 3 | 4 | def check_for_timers(inp, command): 5 | split = inp.split(' ') 6 | timer = 0 7 | print split 8 | if command == 'ban': 9 | lastparam = split[-1].lower() 10 | if 'sec' in lastparam: timer = int(split[-2]) 11 | if 'min' in lastparam: timer = int(split[-2]) * 60 12 | elif 'hour' in lastparam: timer = int(split[-2]) * 60 * 60 13 | elif 'day' in lastparam: timer = int(split[-2]) * 60 * 60 * 24 14 | elif 'week' in lastparam: timer = int(split[-2]) * 60 * 60 * 24 * 7 15 | elif 'month' in lastparam: timer = int(split[-2]) * 60 * 60 * 24 * 30 16 | elif 'year' in lastparam: timer = int(split[-2]) * 60 * 60 * 24 * 365 17 | elif lastparam.isdigit(): timer = int(lastparam) * 60 18 | if command == 'reminder': 19 | if split[1] in [u'sec', u'min', u'hour', u'day', u'week', u'month', u'year']: 20 | lastparam = ' '.join(split[0:2]).lower() 21 | print lastparam 22 | else: 23 | lastparam = split[0].lower() 24 | if 'sec' in lastparam: timer = int(split[0]) 25 | if 'min' in lastparam: timer = int(split[0]) * 60 26 | elif 'hour' in lastparam: timer = int(split[0]) * 60 * 60 27 | elif 'day' in lastparam: timer = int(split[0]) * 60 * 60 * 24 28 | elif 'week' in lastparam: timer = int(split[0]) * 60 * 60 * 24 * 7 29 | elif 'month' in lastparam: timer = int(split[0]) * 60 * 60 * 24 * 30 30 | elif 'year' in lastparam: timer = int(split[0]) * 60 * 60 * 24 * 365 31 | elif lastparam.isdigit(): timer = int(lastparam) * 60 32 | return timer 33 | 34 | def execute(command, conn): 35 | conn.send(command) 36 | 37 | def schedule(timer, priority, command, conn): 38 | s = sched.scheduler(time.time, time.sleep) 39 | s.enter(timer, priority, execute, (command, conn)) 40 | s.run() 41 | 42 | 43 | #from datetime import datetime, timedelta 44 | 45 | # def db_init(db): 46 | # db.execute("create table if not exists scheduler(id primary key, time, action)") 47 | # db.commit() 48 | 49 | 50 | #split = inp.split(' ') 51 | #timer = int(inp[0]) 52 | #action = " ".join(inp[1:]) 53 | #command = 'MODE {} -b {}'.format('#uguubot',action) 54 | 55 | 56 | #run_at = now + timedelta(hours=3) 57 | #delay = (run_at - now).total_seconds() 58 | 59 | # now = datetime.now() 60 | # print now 61 | # change = timedelta(weeks=0, days=0, hours=0, minutes=1, seconds=0) 62 | # print change 63 | # future = now + change 64 | # print future 65 | 66 | # now = datetime.now() 67 | # run_at = now + timedelta(minutes=1) 68 | # delay = (run_at - now).total_seconds() 69 | # threading.Timer(delay, action('test')).start() 70 | #command = 'PRIVMSG {} :{}'.format('#uguubot',inp) 71 | -------------------------------------------------------------------------------- /plugins/util/textgen.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | 4 | TEMPLATE_RE = re.compile(r"\{(.+?)\}") 5 | 6 | 7 | class TextGenerator(object): 8 | def __init__(self, templates, parts, default_templates=None, variables=None): 9 | self.templates = templates 10 | self.default_templates = default_templates 11 | self.parts = parts 12 | self.variables = variables 13 | 14 | def generate_string(self, template=None): 15 | """ 16 | Generates one string using the specified templates. 17 | If no templates are specified, use a random template from the default_templates list. 18 | """ 19 | # this is bad 20 | if self.default_templates: 21 | text = self.templates[template or random.choice(self.default_templates)] 22 | else: 23 | text = random.choice(self.templates) 24 | 25 | # replace static variables in the template with provided values 26 | if self.variables: 27 | for key, value in self.variables.items(): 28 | text = text.replace("{%s}" % key, value) 29 | 30 | # get a list of all text parts we need 31 | required_parts = TEMPLATE_RE.findall(text) 32 | 33 | for required_part in required_parts: 34 | ppart = self.parts[required_part] 35 | # check if the part is a single string or a list 36 | if not isinstance(ppart, basestring): 37 | part = random.choice(self.parts[required_part]) 38 | else: 39 | part = self.parts[required_part] 40 | text = text.replace("{%s}" % required_part, part) 41 | 42 | return text 43 | 44 | def generate_strings(self, amount, template=None): 45 | strings = [] 46 | for i in xrange(amount): 47 | strings.append(self.generate_string()) 48 | return strings 49 | 50 | def get_template(self, template): 51 | return self.templates[template] 52 | -------------------------------------------------------------------------------- /plugins/util/timeformat.py: -------------------------------------------------------------------------------- 1 | from util import text 2 | 3 | def format_time(seconds, count=3, accuracy=6, simple=False): 4 | """ 5 | Takes a length of time in seconds and returns a string describing that length of time. 6 | This function has a number of optional arguments that can be combined: 7 | 8 | SIMPLE: displays the time in a simple format 9 | >>> format_time(SECONDS) 10 | 1 hour, 2 minutes and 34 seconds 11 | >>> format_time(SECONDS, simple=True) 12 | 1h 2m 34s 13 | 14 | COUNT: how many periods should be shown (default 3) 15 | >>> format_time(SECONDS) 16 | 147 years, 9 months and 8 weeks 17 | >>> format_time(SECONDS, count=6) 18 | 147 years, 9 months, 7 weeks, 18 hours, 12 minutes and 34 seconds 19 | """ 20 | 21 | if simple: 22 | periods = [ 23 | ('c', 60 * 60 * 24 * 365 * 100), 24 | ('de', 60 * 60 * 24 * 365 * 10), 25 | ('y', 60 * 60 * 24 * 365), 26 | ('m', 60 * 60 * 24 * 30), 27 | ('d', 60 * 60 * 24), 28 | ('h', 60 * 60), 29 | ('m', 60), 30 | ('s', 1) 31 | ] 32 | else: 33 | periods = [ 34 | (('century', 'centuries'), 60 * 60 * 24 * 365 * 100), 35 | (('decade', 'decades'), 60 * 60 * 24 * 365 * 10), 36 | (('year', 'years'), 60 * 60 * 24 * 365), 37 | (('month', 'months'), 60 * 60 * 24 * 30), 38 | (('day', 'days'), 60 * 60 * 24), 39 | (('hour', 'hours'), 60 * 60), 40 | (('minute', 'minutes'), 60), 41 | (('second', 'seconds'), 1) 42 | ] 43 | 44 | periods = periods[-accuracy:] 45 | 46 | strings = [] 47 | i = 0 48 | for period_name, period_seconds in periods: 49 | if i < count: 50 | if seconds > period_seconds: 51 | period_value, seconds = divmod(seconds, period_seconds) 52 | i += 1 53 | if simple: 54 | strings.append("{}{}".format(period_value, period_name)) 55 | else: 56 | if period_value == 1: 57 | strings.append("{} {}".format(period_value, period_name[0])) 58 | else: 59 | strings.append("{} {}".format(period_value, period_name[1])) 60 | else: 61 | break 62 | 63 | if simple: 64 | return " ".join(strings) 65 | else: 66 | return text.get_text_list(strings, "and") -------------------------------------------------------------------------------- /plugins/util/user.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from util import database 4 | 5 | global userlist 6 | 7 | 8 | def format_hostmask(inp): 9 | "format_hostmask -- Returns a nicks userhost" 10 | return re.sub(r'(@[^@\.]+\d{2,}([^\.]?)+\.)', '*', 11 | inp.replace('@', '@@')).replace('~', '').replace('@@', '@').lower().strip() 12 | 13 | 14 | def get_hostmask(inp, db): 15 | "userhost -- Returns a nicks userhost" 16 | if '@' in inp or '.' in inp: 17 | return inp 18 | nick = inp.strip().replace('~', '').lower() 19 | db_host = database.get(db, 'users', 'mask', 'nick', nick) 20 | if db_host is False: 21 | db_host = database.get(db, 'seen', 'host', 'name', nick) 22 | if db_host is False: 23 | db_host = nick 24 | 25 | return format_hostmask(db_host) 26 | # return db_host 27 | 28 | 29 | def compare_hostmasks(hostmask, matchmasks): 30 | for mask in re.findall(r'(\b\S+\b)', matchmasks): 31 | mask = '^*{}$'.format(mask).replace('.', '\.').replace('*', '.*') 32 | if bool(re.match(mask.lower(), hostmask.lower())): return True 33 | return False 34 | 35 | 36 | def is_globaladmin(hostmask, chan, bot): 37 | globaladmins = ' '.join(bot.config.get('admins', [])) 38 | if globaladmins: 39 | return compare_hostmasks(format_hostmask(hostmask), globaladmins) 40 | return False 41 | 42 | 43 | def is_channeladmin(hostmask, chan, db): 44 | channeladmins = database.get(db, 'channels', 'admins', 'chan', chan) 45 | if channeladmins: 46 | return compare_hostmasks(format_hostmask(hostmask), channeladmins) 47 | return False 48 | 49 | 50 | def is_admin(inp, chan, db, bot): 51 | if is_globaladmin(inp, chan, bot): return True 52 | if is_channeladmin(inp, chan, db): return True 53 | return False 54 | 55 | 56 | # Notes: 57 | # ([^\d]*)?$ 58 | -------------------------------------------------------------------------------- /plugins/util/web.py: -------------------------------------------------------------------------------- 1 | """ web.py - handy functions for web services """ 2 | 3 | import urlnorm 4 | import requests 5 | 6 | 7 | class ShortenError(Exception): 8 | def __init__(self, code, text): 9 | self.code = code 10 | self.text = text 11 | 12 | def __str__(self): 13 | return self.text 14 | 15 | 16 | def isgd(url): 17 | """ shortens a URL with the is.gd API """ 18 | url = urlnorm.normalize(url.encode('utf-8'), assume_scheme='http') 19 | req = requests.get("http://is.gd/create.php", params={'format': 'json', 'url': url}) 20 | 21 | try: 22 | json = req.json() 23 | except ValueError: 24 | print "[!] ERROR: is.gd returned broken json" 25 | raise 26 | 27 | if "errorcode" in json: 28 | raise ShortenError(json["errorcode"], json["errormessage"]) 29 | else: 30 | return json["shorturl"] 31 | 32 | 33 | def try_isgd(url): 34 | return isgd(url) 35 | 36 | 37 | def haste(text, ext='txt'): 38 | """ pastes text to a hastebin server """ 39 | req = requests.post("https://hastebin.com/documents", data=text) 40 | 41 | if req.status_code >= 500 and req.status_code < 600: 42 | print "[!] ERROR: hastebin is down" 43 | return "(error: hastebin is down)" 44 | 45 | try: 46 | data = req.json() 47 | return "https://hastebin.com/raw/{}.{}".format(data['key'], ext) 48 | except ValueError: 49 | print "[!] ERROR: hastebin returned invalid json" 50 | return "(error: hastebin is broken)" 51 | 52 | 53 | def query(query, params={}): 54 | print "[!] ERROR: yql is unavailable but being called" 55 | return None 56 | -------------------------------------------------------------------------------- /plugins/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrozenPigs/Taigabot/2e61213c0793ec11c5b4b97c260f977e8e35c640/plugins/utilities/__init__.py -------------------------------------------------------------------------------- /plugins/utilities/formatting.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | # smush multiple spaces into one 5 | def compress_whitespace(text): 6 | whitespace = re.compile(r"\s+") 7 | return whitespace.sub(' ', text).strip() 8 | 9 | 10 | # replaces newlines (unix or windows) with a space 11 | def remove_newlines(text, separator=' '): 12 | lines = re.compile(r"[\r\n]+") 13 | return lines.sub(separator, text).strip() 14 | -------------------------------------------------------------------------------- /plugins/utilities/iterable.py: -------------------------------------------------------------------------------- 1 | # for when you need to loop a big array but just want the first N items 2 | def limit(j, arr): 3 | i = 0 4 | iterable = iter(arr) 5 | 6 | while True: 7 | yield next(iterable) 8 | i = i + 1 9 | if i == j: 10 | break 11 | -------------------------------------------------------------------------------- /plugins/utilities/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from json import loads as json_load 3 | 4 | from urllib import quote # python 2 5 | # from urllib.parse import quote # python 3 6 | 7 | # update this like once every few months 8 | fake_ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' 9 | 10 | 11 | def urlencode(inp): 12 | def force_decode(string): 13 | for i in ['utf8', 'cp1252']: 14 | try: 15 | return string.decode(i) 16 | except UnicodeDecodeError: 17 | pass 18 | 19 | if isinstance(inp, str): 20 | inp = force_decode(inp) 21 | 22 | return quote(inp.encode('utf8')) 23 | 24 | 25 | def get_json(url, **kwargs): 26 | return json_load(get_text(url, **kwargs)) 27 | 28 | 29 | def get_html(url, **kwargs): 30 | return get(url, **kwargs) 31 | 32 | 33 | def get_text(url, **kwargs): 34 | return get(url, **kwargs) 35 | 36 | 37 | # HTTP GET 38 | # this is probably the function that's used the most in internet-enabled plugins 39 | # it sends an http GET via the "requests" library. 40 | # 41 | # future plugin writers: please use this method so you can easily swap the "backend" 42 | # without replacing hundreds of lines of code all over the codebase 43 | # 44 | # it supports passing an object as the query string 45 | # - example: get('https://google.com/search', params={'q': 'hello world'}) 46 | # - result: https://google.com/search?q=hello%20world 47 | # 48 | # u can also pass custom headers: 49 | # get('http://example.org', headers={'X-secret-key': 'hunter2'}) 50 | # a fake user-agent of a popular browser will be used if none is provided 51 | # 52 | def get(url, **kwargs): 53 | # accept custom headers 54 | if 'headers' in kwargs: 55 | headers = kwargs.pop('headers') 56 | # set a default user-agent if none was set 57 | if 'User-Agent' not in headers: 58 | headers['User-Agent'] = fake_ua 59 | else: 60 | headers = {'User-Agent': fake_ua} 61 | 62 | r = requests.get(url, headers=headers, timeout=8, **kwargs) 63 | return r.text 64 | 65 | 66 | # upload any text to pastebin 67 | # please use this function so you don't have to modify 40 plugins when the api changes 68 | def upload_paste(text, title='Paste', config={}): 69 | # sadly we need to pass bot.config because of the api keys 70 | api_key = config.get('api_keys', {}).get('pastebin', False) 71 | 72 | if api_key is False: 73 | return "no api key found, pls fix config" 74 | 75 | data = { 76 | 'api_dev_key': api_key, 77 | 'api_option': 'paste', 78 | 'api_paste_code': text, 79 | 'api_paste_name': title, 80 | 'api_paste_private': 1, 81 | 'api_paste_expire_date': '1D', 82 | } 83 | 84 | response = requests.post('https://pastebin.com/api/api_post.php', headers={'User-Agent': fake_ua}, data=data, timeout=12, allow_redirects=True) 85 | return response.text 86 | -------------------------------------------------------------------------------- /plugins/utilities/services.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | user = 'taigabot' 4 | 5 | HEADERS = {'User-Agent': 'taigabot, python irc bot'} 6 | 7 | 8 | def paste_taigalink(text, title='Paste'): 9 | data = { 10 | 'title': title, 11 | 'uploader': 'taigabot', 12 | 'text': text 13 | } 14 | res = requests.post('https://taiga.link/p/upload', headers=HEADERS, data=data) 15 | return res.text 16 | 17 | 18 | # leaving this one here in case taigalink dies 19 | def paste_pastebin(text, title='Paste', config={}): 20 | # sadly we need to pass bot.config because of the api keys 21 | api_key = config.get('api_keys', {}).get('pastebin', False) 22 | 23 | if api_key is False: 24 | return "no api key found, pls fix config" 25 | 26 | data = { 27 | 'api_dev_key': api_key, 28 | 'api_option': 'paste', 29 | 'api_paste_code': text, 30 | 'api_paste_name': title, 31 | 'api_paste_private': 1, 32 | 'api_paste_expire_date': '1D', 33 | } 34 | 35 | response = requests.post('https://pastebin.com/api/api_post.php', headers={'User-Agent': fake_ua}, data=data, timeout=12, allow_redirects=True) 36 | return response.text 37 | 38 | 39 | def shorten_taigalink(url): 40 | data = { 41 | 'title': title, 42 | 'uploader': 'taigabot', 43 | 'text': text 44 | } 45 | res = requests.post('https://taiga.link/s/short', headers={'User-Agent': 'taigabot'}, data=data) 46 | return res.text 47 | 48 | 49 | # upload any text to pastebin 50 | # please use this function so you don't have to modify 40 plugins when the api changes 51 | def paste(text, title='Paste'): # paste(*args, **kwargs)? 52 | return paste_taigalink(text, title) 53 | 54 | 55 | def shorten(url): 56 | return shorten_taigalink(url) 57 | -------------------------------------------------------------------------------- /plugins/validate.py: -------------------------------------------------------------------------------- 1 | # w3c validator plugin by ine (2020) 2 | from util import hook 3 | from utilities import request 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | @hook.command('w3c') 8 | @hook.command 9 | def validate(inp): 10 | """validate <url> -- Runs url through the w3c markup validator.""" 11 | 12 | if not inp.startswith('http'): 13 | inp = 'https://' + inp 14 | 15 | url = 'https://validator.w3.org/nu/?doc=' + request.urlencode(inp) 16 | html = request.get(url) 17 | soup = BeautifulSoup(html, 'lxml') 18 | results = soup.find('div', attrs={'id': 'results'}) 19 | 20 | errors = len(results.find_all('li', attrs={'class': 'error'})) 21 | warns = len(results.find_all('li', attrs={'class': 'warning'})) 22 | info = len(results.find_all('li', attrs={'class': 'info'})) 23 | 24 | if errors == 0 and warns == 0 and info == 0: 25 | return "[w3c] Successfully validated with no errors" 26 | 27 | return "[w3c] Found {} errors, {} warnings and {} notices.".format(errors, warns, info) 28 | -------------------------------------------------------------------------------- /plugins/vimeo.py: -------------------------------------------------------------------------------- 1 | # vimeo info plugin by ine (2020) 2 | from util import hook, timeformat 3 | from utilities import request 4 | 5 | # the v2 api is deprecated and only does simple public video information 6 | # new one is at https://api.vimeo.com/videos/{id} but needs a key 7 | 8 | 9 | def info(id): 10 | info = request.get_json('http://vimeo.com/api/v2/video/' + id + '.json') 11 | 12 | if not info or len(info) == 0: 13 | return 14 | 15 | title = info[0]['title'] 16 | length = timeformat.format_time(info[0]["duration"], simple=True) 17 | likes = format(info[0]['stats_number_of_likes'], ',d') 18 | views = format(info[0]['stats_number_of_plays'], ',d') 19 | uploader = info[0]['user_name'] 20 | upload_date = info[0]['upload_date'] 21 | 22 | output = [] 23 | output.append('\x02' + title + '\x02') 24 | output.append('length \x02' + length + '\x02') 25 | output.append(likes + ' likes') 26 | output.append(views + ' views') 27 | output.append('\x02' + uploader + '\x02 on ' + upload_date) 28 | 29 | return ' - '.join(output) 30 | 31 | 32 | @hook.regex(r'https?://player\.vimeo\.com/video/([0-9]+)') 33 | @hook.regex(r'https?://vimeo\.com/([0-9]+)/?') 34 | def vimeo_url(match): 35 | """<vimeo url> -- automatically returns information on the Vimeo video at <url>""" 36 | output = info(match.group(1)) 37 | 38 | if not output: 39 | return 40 | 41 | return output 42 | -------------------------------------------------------------------------------- /plugins/wordoftheday.py: -------------------------------------------------------------------------------- 1 | # word of the day plugin by ine (2020) 2 | from util import hook 3 | from utilities import request, iterable 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | @hook.command() 8 | def wordoftheday(inp): 9 | html = request.get('https://www.merriam-webster.com/word-of-the-day') 10 | soup = BeautifulSoup(html) 11 | 12 | word = soup.find('div', attrs={'class': 'word-and-pronunciation'}).find('h1').text 13 | paragraphs = soup.find('div', attrs={'class': 'wod-definition-container'}).find_all('p') 14 | 15 | definitions = [] 16 | 17 | for paragraph in iterable.limit(4, paragraphs): 18 | definitions.append(paragraph.text.strip()) 19 | 20 | output = u"The word of the day is \x02{}\x02: {}".format(word, '; '.join(definitions)) 21 | 22 | if len(output) > 320: 23 | output = output[:320] + '... More at https://www.merriam-webster.com/word-of-the-day' 24 | 25 | return output 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | BeautifulSoup4==4.9.0 2 | lxml==3.3.6 3 | requests 4 | -------------------------------------------------------------------------------- /requirements_broken.txt: -------------------------------------------------------------------------------- 1 | BeautifulSoup==3.2.1 -------------------------------------------------------------------------------- /requirements_extra.txt: -------------------------------------------------------------------------------- 1 | geopy==1.22.0 2 | pytz==2020.1 3 | tweepy==3.5.0 4 | mygengo 5 | pyparsing==2.4.7 6 | # this doesn't work on the dockerfile, comment out 7 | pyenchant==1.6.11 8 | httplib2 9 | psutil 10 | -------------------------------------------------------------------------------- /uguubot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "" 3 | echo " __ __ " 4 | echo " / / / /___ ___ ____ __" 5 | echo " / / / / __ / / / / / / /" 6 | echo "/ /_/ / /_/ / /_/ / /_/ / " 7 | echo "\____/\__, /\__,_/\__,_/ " 8 | echo " /____/ " 9 | echo "" 10 | 11 | 12 | locatefiles() { 13 | botfile="/bot.py" 14 | botfile=$(pwd)$botfile 15 | logfile="/bot.log" 16 | logfile=$(pwd)$logfile 17 | } 18 | 19 | running() { 20 | if [[ $(ps aux|grep bot.py|grep -v grep|grep -v daemon|grep -v SCREEN) != "" ]]; then 21 | true 22 | else 23 | false 24 | fi 25 | } 26 | 27 | checkbackend() { 28 | if dpkg -l| grep ^ii|grep daemon|grep 'turns other' > /dev/null; then 29 | backend="daemon" 30 | elif dpkg -l| grep ^ii|grep screen|grep 'terminal multi' > /dev/null; then 31 | backend="screen" 32 | else 33 | backend="manual" 34 | fi 35 | return 0 36 | } 37 | 38 | setcommands() { 39 | status() { 40 | if running; then 41 | echo "UguuBot is running!" 42 | else 43 | echo "UguuBot is not running!" 44 | fi 45 | } 46 | clear() { 47 | : > $logfile 48 | } 49 | if [ "$backend" == "daemon" ]; then 50 | start() { 51 | daemon -r -n uguubot -O $logfile python $botfile 52 | } 53 | stop() { 54 | daemon -n uguubot --stop 55 | } 56 | elif [ "$backend" == "screen" ]; then 57 | start() { 58 | screen -d -m -S uguubot -t uguubot python $botfile > $logfile 2>&1 59 | } 60 | stop() { 61 | pid=`ps ax|grep -v grep|grep python|grep -v SCREEN|grep $botfile|awk '{print $1}'` 62 | kill $pid 63 | } 64 | elif [ "$backend" == "manual" ]; then 65 | start() { 66 | $botfile 67 | } 68 | stop() { 69 | pid=`ps ax|grep -v grep|grep python|grep $botfile|awk '{print $1}'` 70 | kill $pid 71 | } 72 | fi 73 | } 74 | 75 | processargs() { 76 | case $1 in 77 | start|-start|--start) 78 | if running; then 79 | echo "Cannot start! Bot is already running!" 80 | exit 1 81 | else 82 | echo "Starting UguuBot... ($backend)" 83 | start 84 | fi 85 | ;; 86 | stop|-stop|--stop) 87 | if running; then 88 | echo "Stopping UguuBot... ($backend)" 89 | stop 90 | else 91 | echo "Cannot stop! Bot is not already running!" 92 | exit 1 93 | fi 94 | ;; 95 | restart|-restart|--restart) 96 | if running; then 97 | echo "Restarting UguuBot... ($backend)" 98 | stop 99 | sleep 3 100 | start 101 | else 102 | echo "Cannot restart! Bot is not already running!" 103 | exit 1 104 | fi 105 | ;; 106 | clear|-clear|--clear) 107 | echo "Clearing logs..." 108 | clear 109 | ;; 110 | status|-status|--status) 111 | status 112 | ;; 113 | *) 114 | usage="usage: ./uguubot {start|stop|restart|clear|status}" 115 | echo $usage 116 | ;; 117 | esac 118 | } 119 | 120 | main() { 121 | locatefiles 122 | checkbackend 123 | setcommands 124 | processargs $1 125 | } 126 | 127 | main $* 128 | exit 0 --------------------------------------------------------------------------------