├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Vagrantfile ├── cloudbot ├── __init__.py ├── __main__.py ├── bot.py ├── client.py ├── clients │ ├── __init__.py │ └── irc.py ├── config.py ├── event.py ├── hook.py ├── permissions.py ├── plugin.py ├── reloader.py └── util │ ├── __init__.py │ ├── async_util.py │ ├── colors.py │ ├── database.py │ ├── filesize.py │ ├── formatting.py │ ├── func_utils.py │ ├── http.py │ ├── pager.py │ ├── parsers │ ├── __init__.py │ └── irc.py │ ├── sequence.py │ ├── test │ ├── test_colors.py │ ├── test_database.py │ ├── test_filesize.py │ ├── test_formatting.py │ ├── test_timeformat.py │ └── test_tokenbucket.py │ ├── textgen.py │ ├── timeformat.py │ ├── timeparse.py │ ├── tokenbucket.py │ └── web.py ├── config.default.json ├── data ├── 8ball_responses.txt ├── attacks │ ├── bdsm.json │ ├── bite.json │ ├── clinton.json │ ├── compliment.json │ ├── fight.json │ ├── flirt.json │ ├── glomp.json │ ├── highfive.json │ ├── hug.json │ ├── insult.json │ ├── kill.json │ ├── lart.json │ ├── lurve.json │ ├── nk.json │ ├── present.json │ ├── slap.json │ ├── spank.json │ ├── strax.json │ └── trump.json ├── book_puns.txt ├── cheers.txt ├── confucious.txt ├── do_it.txt ├── drinks.json ├── fmk.txt ├── foaas.json ├── food │ ├── beer.json │ ├── brekkie.json │ ├── burger.json │ ├── cake.json │ ├── cereal.json │ ├── cheese.json │ ├── chicken.json │ ├── chocolate.json │ ├── coffee.json │ ├── cookies.json │ ├── donut.json │ ├── doobie.json │ ├── halal.json │ ├── icecream.json │ ├── kebab.json │ ├── keto.json │ ├── kosher.json │ ├── milkshake.json │ ├── muffin.json │ ├── noodles.json │ ├── nugget.json │ ├── pancake.json │ ├── pasta.json │ ├── pie.json │ ├── pizza.json │ ├── potato.json │ ├── rice.json │ ├── sandwich.json │ ├── scone.json │ ├── soup.json │ ├── steak.json │ ├── sushi.json │ ├── taco.json │ ├── tea.json │ └── wine.json ├── fortunes.txt ├── gnomecards.json ├── hookup.json ├── kenm.txt ├── kero.txt ├── lawyerjoke.txt ├── leet.json ├── lenny.json ├── 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 ├── one_liners.txt ├── password_words.txt ├── puns.txt ├── reaction_macros.json ├── topicchange.txt ├── wisdom.txt └── yo_momma.txt ├── docs ├── README.md ├── dev │ └── main.md └── user │ ├── commands.md │ ├── configuration.md │ ├── googlecustomsearch_id.md │ ├── googledevconsole_api.md │ ├── img │ ├── cse_1.png │ ├── cse_2.png │ ├── cse_3.png │ ├── gdev_1.png │ ├── gdev_2.png │ ├── gdev_3.png │ ├── gdev_4.png │ ├── gdev_5.png │ ├── gdev_6.png │ ├── gdev_7.png │ ├── gdev_8.png │ ├── oc_1.png │ ├── oc_2.png │ ├── oc_3.png │ └── wn_1.png │ ├── main_user.md │ ├── octopart_api.md │ ├── optout.md │ └── wordnik_api.md ├── format_json.py ├── plugins ├── __init__.py ├── admin_bot.py ├── admin_channel.py ├── amazon.py ├── animal_gifs.py ├── attacks.py ├── autojoin.py ├── badwords.py ├── bible.py ├── bing.py ├── books.py ├── brainfuck.py ├── brew.py ├── cats.py ├── chain.py ├── chan_track.py ├── chatbot.py ├── cheer.py ├── core │ ├── __init__.py │ ├── cap.py │ ├── chan_log.py │ ├── check_conn.py │ ├── core_connect.py │ ├── core_ctcp.py │ ├── core_hooks.py │ ├── core_misc.py │ ├── core_out.py │ ├── core_sieve.py │ ├── core_tracker.py │ ├── help.py │ ├── optout.py │ ├── plugin_control.py │ ├── regex_chans.py │ └── server_info.py ├── correction.py ├── cryptocurrency.py ├── cypher.py ├── deals.py ├── dig.py ├── dogpile.py ├── domainr.py ├── dragonvale.py ├── dramatica.py ├── drinks.py ├── duckhunt.py ├── eightball.py ├── etymology.py ├── fact.py ├── factoids.py ├── feeds.py ├── fishbans.py ├── flip.py ├── fmk.py ├── foaas.py ├── foods.py ├── fortune.py ├── gaming.py ├── geoip.py ├── giphy.py ├── github.py ├── gnomeagainsthumanity.py ├── google.py ├── google_cse.py ├── google_translate.py ├── googleurlparse.py ├── grab.py ├── herald.py ├── history.py ├── hook_stats.py ├── hookup.py ├── horoscope.py ├── ignore.py ├── imdb.py ├── imgur.py ├── issafe.py ├── jokes.py ├── karma.py ├── kenm.py ├── lastfm.py ├── lenny.py ├── librefm.py ├── link_announcer.py ├── linux.py ├── lmgtfy.py ├── locate.py ├── log.py ├── lyricsnmusic.py ├── metacritic.py ├── metars.py ├── minecraft_ping.py ├── minecraft_status.py ├── minecraft_user.py ├── minecraft_wiki.py ├── mock.py ├── monsterhunt.py ├── myfitnesspal.py ├── mylife.py ├── name_generator.py ├── newegg.py ├── notes.py ├── octopart.py ├── pagecheck.py ├── password.py ├── piglatin.py ├── ping.py ├── plpaste.py ├── poll.py ├── profile.py ├── profiling.py ├── quote.py ├── quran.py ├── randomusefulwebsites.py ├── reactions.py ├── recipe.py ├── reddit.py ├── reddit_info.py ├── remind.py ├── rottentomatoes.py ├── rua.py ├── sasl.py ├── scene.py ├── shorten.py ├── shrug.py ├── snopes.py ├── soundcloud.py ├── speedtest.py ├── spellcheck.py ├── sportscores.py ├── spotify.py ├── steam_store.py ├── steam_user.py ├── steamdb.py ├── stock.py ├── suggest.py ├── system.py ├── tell.py ├── thefuckingweather.py ├── time_plugin.py ├── topicchange.py ├── tvdb.py ├── twitch.py ├── twitter.py ├── urban.py ├── utility.py ├── validate.py ├── vimeo.py ├── voat.py ├── weather.py ├── whois.py ├── wikipedia.py ├── wolframalpha.py ├── wordnik.py ├── wyr.py ├── xkcd.py ├── yandex_translate.py ├── yelling.py └── youtube.py ├── requirements.txt ├── tests ├── core_tests │ ├── __init__.py │ └── test_plugin_hooks.py └── plugin_tests │ ├── __init__.py │ ├── test_fishbans.py │ └── test_link_announcer.py ├── travis ├── pylintrc ├── requirements.txt └── test_json.py └── vagrant-bootstrap.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # CloudBot editor configuration normalization 2 | # Copied from Drupal (GPL) 3 | # @see http://editorconfig.org/ 4 | 5 | # This is the top-most .editorconfig file; do not search in parent directories. 6 | root = true 7 | 8 | # All files. 9 | [*] 10 | end_of_line = LF 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # Not in the spec yet: 15 | # @see https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # default all files as text with lf 2 | * text eol=lf 3 | 4 | # mmdb files are binary 5 | *.mmdb binary 6 | 7 | # PNGs are also binary (it broke the docs) 8 | *.png binary 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Sphinx documentation 48 | docs/_build/ 49 | 50 | # PyBuilder 51 | target/ 52 | 53 | # Cloudbot 54 | persist/ 55 | logs/ 56 | config.json 57 | *.db 58 | *.mmdb 59 | *.log 60 | .idea/ 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.5" 5 | - "3.6" 6 | - "3.7-dev" 7 | - "nightly" 8 | - "pypy3" 9 | 10 | cache: pip 11 | 12 | install: 13 | - "sudo apt-get update -q" 14 | - "sudo apt-get install -y python3-lxml libenchant-dev" 15 | - "pip install -r ./travis/requirements.txt" 16 | 17 | script: 18 | - "py.test . -R : -v --cov . --cov-report term-missing --pylint --pylint-rcfile=travis/pylintrc" 19 | 20 | after_success: 21 | - "coveralls" 22 | 23 | env: 24 | - PYTHONPATH=. 25 | - PYTHONPATH=. PYTHONASYNCIODEBUG=1 26 | 27 | matrix: 28 | allow_failures: 29 | - python: "3.7-dev" 30 | - python: "nightly" 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ## 1.0.9 4 | TBA 5 | 6 | ### 1.0.8 7 | This update is pretty big. Be warned. 8 | * Improved flip command. 9 | * Added new time command that gets time for location. 10 | * Added locate command that locates a place on Google Maps. 11 | * Change weather command to use new location API from the two above. 12 | * Added more kill messages. 13 | * Revamp lastfm with more commands and better memory. 14 | * Add new poll command. Still not perfect. 15 | * Replaced old dictionary plugin with new Wordnik plugin. 16 | * Revamped Soundcloud command. 17 | * Revamped chatbot command. 18 | * Switched back to google search. 19 | * Added new issafe plugin. 20 | * And a whole lot of minor tweaks and fixes. 21 | 22 | ### 1.0.7.1 23 | * Security fixes. 24 | 25 | ### 1.0.7 26 | * Added new "Would you rather" plugin. 27 | 28 | ### 1.0.6 29 | * Added pig latin translator, requires new *nltk* module 30 | * Added reminder command 31 | * Added new periodic hook (does not support reloading properly yet, so use with caution) 32 | * Added priority sorting to sieve hooks 33 | * Started work on new documentation for 1.1 34 | * Did some minor internal refactoring 35 | 36 | **1.0.5** - Fix geoip for queries with no region, fix youtube bug, add flip command 37 | 38 | **1.0.4** - Adjust ratelimiter cleanup task, add octopart API key, fix brainfuck, sort mcstatus output. 39 | 40 | **1.0.3** - More minor changes to plugins, fixed rate-limiting properly, banished SCP to CloudBotIRC/Plugins, added wildcard support to permissions (note: don't use this yet, it's still not entirely finalized!) 41 | 42 | **1.0.2** - Minor internal changes and fixes, banished minecraft_bukget and worldofwarcraft to CloudBotIRC/Plugins 43 | 44 | **1.0.1** - Fix history.py tracking 45 | 46 | **1.0.0** - Initial stable release 47 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | VAGRANTFILE_API_VERSION = "2" 2 | 3 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 4 | config.vm.box = "ubuntu/trusty32" 5 | config.vm.provision :shell, path: "vagrant-bootstrap.sh" 6 | end 7 | -------------------------------------------------------------------------------- /cloudbot/clients/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/cloudbot/clients/__init__.py -------------------------------------------------------------------------------- /cloudbot/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import sys 5 | import time 6 | from collections import OrderedDict 7 | 8 | logger = logging.getLogger("cloudbot") 9 | 10 | 11 | class Config(OrderedDict): 12 | """ 13 | :type filename: str 14 | :type path: str 15 | :type bot: cloudbot.bot.CloudBot 16 | """ 17 | 18 | def __init__(self, bot, *args, **kwargs): 19 | """ 20 | :type bot: cloudbot.bot.CloudBot 21 | :type args: list 22 | :type kwargs: dict 23 | """ 24 | super().__init__(*args, **kwargs) 25 | self.filename = "config.json" 26 | self.path = os.path.abspath(self.filename) 27 | self.bot = bot 28 | self.update(*args, **kwargs) 29 | 30 | # populate self with config data 31 | self.load_config() 32 | 33 | def load_config(self): 34 | """(re)loads the bot config from the config file""" 35 | if not os.path.exists(self.path): 36 | # if there is no config, show an error and die 37 | logger.critical("No config file found, bot shutting down!") 38 | print("No config file found! Bot shutting down in five seconds.") 39 | print("Copy 'config.default.json' to 'config.json' for defaults.") 40 | print("For help, see http://git.io/cloudbotirc. Thank you for using CloudBot!") 41 | time.sleep(5) 42 | sys.exit() 43 | 44 | with open(self.path) as f: 45 | data = json.load(f, object_pairs_hook=OrderedDict) 46 | 47 | self.update(data) 48 | logger.debug("Config loaded from file.") 49 | 50 | # reload permissions 51 | if self.bot.connections: 52 | for connection in self.bot.connections.values(): 53 | connection.permissions.reload() 54 | 55 | def save_config(self): 56 | """saves the contents of the config dict to the config file""" 57 | with open(self.path, 'w') as f: 58 | json.dump(self, f, indent=4) 59 | 60 | logger.info("Config saved to file.") 61 | -------------------------------------------------------------------------------- /cloudbot/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/cloudbot/util/__init__.py -------------------------------------------------------------------------------- /cloudbot/util/async_util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wraps various asyncio functions 3 | """ 4 | 5 | import asyncio 6 | import sys 7 | from functools import partial 8 | 9 | from cloudbot.util.func_utils import call_with_args 10 | 11 | 12 | def wrap_future(fut, *, loop=None): 13 | """ 14 | Wraps asyncio.async()/asyncio.ensure_future() depending on the python version 15 | :param fut: The awaitable, future, or coroutine to wrap 16 | :param loop: The loop to run in 17 | :return: The wrapped future 18 | """ 19 | if sys.version_info < (3, 4, 4): 20 | # This is to avoid a SyntaxError on 3.7.0a2+ 21 | func = getattr(asyncio, "async") 22 | else: 23 | func = asyncio.ensure_future 24 | 25 | return func(fut, loop=loop) # pylint: disable=locally-disabled, deprecated-method 26 | 27 | 28 | @asyncio.coroutine 29 | def run_func(loop, func, *args, **kwargs): 30 | part = partial(func, *args, **kwargs) 31 | if asyncio.iscoroutine(func) or asyncio.iscoroutinefunction(func): 32 | return (yield from part()) 33 | else: 34 | return (yield from loop.run_in_executor(None, part)) 35 | 36 | 37 | @asyncio.coroutine 38 | def run_func_with_args(loop, func, arg_data, executor=None): 39 | if asyncio.iscoroutine(func): 40 | raise TypeError('A coroutine function or a normal, non-async callable are required') 41 | 42 | if asyncio.iscoroutinefunction(func): 43 | coro = call_with_args(func, arg_data) 44 | else: 45 | coro = loop.run_in_executor(executor, call_with_args, func, arg_data) 46 | 47 | return (yield from coro) 48 | 49 | 50 | def run_coroutine_threadsafe(coro, loop): 51 | """ 52 | Runs a coroutine in a threadsafe manner 53 | :type coro: coroutine 54 | :type loop: asyncio.AbstractEventLoop 55 | """ 56 | if not asyncio.iscoroutine(coro): 57 | raise TypeError('A coroutine object is required') 58 | 59 | if sys.version_info < (3, 5, 1): 60 | loop.call_soon_threadsafe(partial(wrap_future, coro, loop=loop)) 61 | else: 62 | asyncio.run_coroutine_threadsafe(coro, loop) 63 | 64 | 65 | def create_future(loop=None): 66 | if loop is None: 67 | loop = asyncio.get_event_loop() 68 | 69 | if sys.version_info < (3, 5, 2): 70 | return asyncio.Future(loop=loop) 71 | 72 | return loop.create_future() 73 | -------------------------------------------------------------------------------- /cloudbot/util/database.py: -------------------------------------------------------------------------------- 1 | """ 2 | database - contains variables set by cloudbot to be easily access 3 | """ 4 | 5 | # this is assigned in the CloudBot so that its recreated when the bot restarts 6 | metadata = None 7 | base = None 8 | -------------------------------------------------------------------------------- /cloudbot/util/func_utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | 4 | class ParameterError(Exception): 5 | def __init__(self, name, valid_args): 6 | self.__init__(name, list(valid_args)) 7 | 8 | def __str__(self): 9 | return "'{}' is not a valid parameter, valid parameters are: {}".format(self.args[0], self.args[1]) 10 | 11 | 12 | def call_with_args(func, arg_data): 13 | sig = inspect.signature(func) 14 | try: 15 | args = [arg_data[key] for key in sig.parameters.keys() if not key.startswith('_')] 16 | except KeyError as e: 17 | raise ParameterError(e.args[0], arg_data.keys()) from e 18 | 19 | return func(*args) 20 | -------------------------------------------------------------------------------- /cloudbot/util/pager.py: -------------------------------------------------------------------------------- 1 | from threading import RLock 2 | 3 | from cloudbot.util.sequence import chunk_iter 4 | 5 | 6 | class Pager: 7 | """Multiline pager 8 | 9 | Takes a string with newlines and paginates it to certain size chunks 10 | """ 11 | 12 | @classmethod 13 | def from_multiline_string(cls, s): 14 | return cls(s.splitlines()) 15 | 16 | def __init__(self, lines, chunk_size=2): 17 | # This lock should always be acquired when accessing data from this object 18 | # Added here due to extensive use of threads throughout plugins 19 | self.lock = RLock() 20 | self.chunk_size = chunk_size 21 | self.chunks = tuple(chunk_iter(lines, self.chunk_size)) 22 | self.current_pos = 0 23 | 24 | def format_chunk(self, chunk, pagenum): 25 | chunk = list(chunk) 26 | if len(self.chunks) > 1: 27 | chunk[-1] += " (page {}/{})".format(pagenum + 1, len(self.chunks)) 28 | 29 | return chunk 30 | 31 | def next(self): 32 | with self.lock: 33 | if self.current_pos >= len(self.chunks): 34 | return None 35 | 36 | chunk = self[self.current_pos] 37 | self.current_pos += 1 38 | 39 | return chunk 40 | 41 | def get(self, index): 42 | """Get a specific page""" 43 | return self[index] 44 | 45 | def __getitem__(self, item): 46 | """Get a specific page""" 47 | with self.lock: 48 | chunk = self.chunks[item] 49 | return self.format_chunk(chunk, item) 50 | 51 | def __len__(self): 52 | with self.lock: 53 | return len(self.chunks) 54 | 55 | 56 | def paginated_list(data, delim=" \u2022 ", suffix='...', max_len=256, page_size=2): 57 | lines = [""] 58 | for item in data: 59 | if len(item) > max_len: 60 | # The length of a single item is longer then our max line length, split it 61 | lines.append(item[:max_len]) 62 | lines.append(item[max_len:]) 63 | elif len(lines[-1]) + len(item) > max_len: 64 | lines.append(item) 65 | else: 66 | if lines[-1]: 67 | lines[-1] += delim 68 | 69 | lines[-1] += item 70 | 71 | formatted_lines = [] 72 | while lines: 73 | line = lines.pop(0) 74 | formatted_lines.append("{}{}".format(line, suffix if lines else "")) 75 | 76 | return Pager(formatted_lines, chunk_size=page_size) 77 | -------------------------------------------------------------------------------- /cloudbot/util/parsers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/cloudbot/util/parsers/__init__.py -------------------------------------------------------------------------------- /cloudbot/util/sequence.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sequence utilities - Various util functions for working with lists, sets, tuples, etc 3 | """ 4 | 5 | 6 | def chunk_iter(data, chunk_size): 7 | """ 8 | Splits a sequence in to chunks 9 | :param data: The sequence to split 10 | :param chunk_size: The maximum size of each chunk 11 | :return: An iterable of all the chunks of the sequence 12 | """ 13 | for i in range(0, len(data), chunk_size): 14 | yield data[i:i + chunk_size] 15 | -------------------------------------------------------------------------------- /cloudbot/util/test/test_colors.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cloudbot.util.colors import parse, strip, get_available_colours, get_available_formats, get_color, get_format, \ 4 | _convert, strip_irc, strip_all, IRC_COLOUR_DICT 5 | 6 | test_input = "The quick $(brown, red)brown$(clear) fox$(fake) jumps over the $(bold)lazy dog$(clear)." 7 | 8 | test_parse_output = "The quick \x0305,04brown\x0f fox jumps over the \x02lazy dog\x0f." 9 | test_strip_output = "The quick brown fox jumps over the lazy dog." 10 | 11 | test_strip_irc_input = "\x02I am $(bold)bold\x02" 12 | test_strip_irc_result = "I am $(bold)bold" 13 | test_strip_all_result = "I am bold" 14 | 15 | 16 | def test_parse(): 17 | assert parse(test_input) == test_parse_output 18 | 19 | 20 | def test_strip(): 21 | assert strip(test_input) == test_strip_output 22 | assert strip_irc(test_strip_irc_input) == test_strip_irc_result 23 | assert strip_all(test_strip_irc_input) == test_strip_all_result 24 | 25 | 26 | def test_available_colors(): 27 | assert "dark_grey" in get_available_colours() 28 | 29 | 30 | def test_available_formats(): 31 | assert "bold" in get_available_formats() 32 | 33 | 34 | def test_invalid_color(): 35 | with pytest.raises(KeyError) as excinfo: 36 | get_color("cake") 37 | assert 'not in the list of available colours' in str(excinfo.value) 38 | 39 | 40 | def test_invalid_format(): 41 | with pytest.raises(KeyError) as excinfo: 42 | get_format("cake") 43 | assert 'not found in the list of available formats' in str(excinfo.value) 44 | 45 | 46 | def test_get_color(): 47 | assert get_color("red") == "\x0304" 48 | assert get_color("red", return_formatted=False) == "04" 49 | 50 | 51 | def test_get_random_color(): 52 | assert get_color("random") in ["\x03" + i for i in IRC_COLOUR_DICT.values()] 53 | assert get_color("random", return_formatted=False) in list(IRC_COLOUR_DICT.values()) 54 | 55 | 56 | def test_get_format(): 57 | assert get_format("bold") == "\x02" 58 | 59 | 60 | def test_convert(): 61 | assert _convert("$(red, green)") == "\x0304,09" 62 | assert _convert("$(red, bold)") == "\x0304\x02" 63 | assert _convert("$(red)") == "\x0304" 64 | assert _convert("$(bold)") == "\x02" 65 | assert _convert("cats") == "cats" 66 | -------------------------------------------------------------------------------- /cloudbot/util/test/test_database.py: -------------------------------------------------------------------------------- 1 | from cloudbot.util.database import metadata, base 2 | 3 | 4 | def test_database(): 5 | assert metadata is None 6 | assert base is None 7 | -------------------------------------------------------------------------------- /cloudbot/util/test/test_filesize.py: -------------------------------------------------------------------------------- 1 | import cloudbot.util.filesize as fs 2 | from cloudbot.util.filesize import size, si, verbose 3 | 4 | 5 | def test_size(): 6 | # Using the traditional system, where a factor of 1024 is used 7 | assert size(10) == "10B" 8 | assert size(100) == "100B" 9 | assert size(1000) == "1000B" 10 | assert size(2000) == "1K" 11 | assert size(10000) == "9K" 12 | assert size(20000) == "19K" 13 | assert size(100000) == "97K" 14 | assert size(200000) == "195K" 15 | assert size(1000000) == "976K" 16 | assert size(2000000) == "1M" 17 | 18 | 19 | def test_size_verbose(): 20 | # Using the verbose system, where a factor of 1024 is used 21 | assert size(1, system=verbose) == "1 byte" 22 | assert size(1000, system=verbose) == "1000 bytes" 23 | assert size(2000, system=verbose) == "1 kilobyte" 24 | assert size(10000, system=verbose) == "9 kilobytes" 25 | assert size(2000000, system=verbose) == "1 megabyte" 26 | assert size(30000000, system=verbose) == "28 megabytes" 27 | 28 | 29 | def test_size_si(): 30 | # Using the SI system, with a factor of 1000 31 | assert size(10, system=si) == "10B" 32 | assert size(100, system=si) == "100B" 33 | assert size(1000, system=si) == "1K" 34 | assert size(2000, system=si) == "2K" 35 | assert size(10000, system=si) == "10K" 36 | assert size(20000, system=si) == "20K" 37 | assert size(100000, system=si) == "100K" 38 | assert size(200000, system=si) == "200K" 39 | assert size(1000000, system=si) == "1M" 40 | assert size(2000000, system=si) == "2M" 41 | 42 | 43 | def test_size_alias(): 44 | assert size(1, system=fs.V) == "1 byte" 45 | -------------------------------------------------------------------------------- /cloudbot/util/test/test_timeformat.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from cloudbot.util.timeformat import format_time, time_since, time_until 4 | 5 | 6 | def test_format_time(): 7 | # basic 8 | assert format_time(120000) == "1 day, 9 hours and 20 minutes" 9 | assert format_time(120000, simple=True) == "1d 9h 20m" 10 | # count 11 | assert format_time(1200003, count=4) == "13 days, 21 hours, 20 minutes and 3 seconds" 12 | assert format_time(1200000, count=4) == "13 days, 21 hours and 20 minutes" 13 | assert format_time(1200000, count=2) == "13 days and 21 hours" 14 | 15 | 16 | def test_timesince(): 17 | then = datetime(2010, 4, 12, 12, 30, 0) 18 | then_timestamp = 1271075400.0 19 | then_future = datetime(2012, 4, 12, 12, 30, 0) 20 | now = datetime(2010, 5, 15, 1, 50, 0) 21 | now_timestamp = 1273888200.0 22 | # timestamp 23 | assert time_since(then_timestamp, now_timestamp) == "1 month and 2 days" 24 | # basic 25 | assert time_since(then, now) == "1 month and 2 days" 26 | # count 27 | assert time_since(then, now, count=3) == "1 month, 2 days and 13 hours" 28 | # future 29 | assert time_since(then_future, now) == "0 minutes" 30 | 31 | 32 | def test_timeuntil(): 33 | now = datetime(2010, 4, 12, 12, 30, 0) 34 | future = datetime(2010, 5, 15, 1, 50, 0) 35 | # basic 36 | assert time_until(future, now) == "1 month and 2 days" 37 | # count 38 | assert time_until(future, now, count=3) == "1 month, 2 days and 13 hours" 39 | -------------------------------------------------------------------------------- /cloudbot/util/test/test_tokenbucket.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from cloudbot.util.tokenbucket import TokenBucket 4 | 5 | 6 | # noinspection PyProtectedMember 7 | def test_bucket_consume(): 8 | bucket = TokenBucket(10, 5) 9 | # larger then capacity 10 | assert bucket.consume(15) is False 11 | # success 12 | assert bucket.consume(10) is True 13 | # check if bucket has no tokens 14 | assert bucket._tokens == 0 15 | # bucket is empty from above, should fail 16 | assert bucket.consume(10) is False 17 | 18 | 19 | # noinspection PyProtectedMember 20 | def test_bucket_advanced(): 21 | bucket = TokenBucket(10, 1) 22 | # tokens start at 10 23 | assert bucket._tokens == 10 24 | # empty tokens 25 | assert bucket.empty() is True 26 | # check tokens is 0 27 | assert bucket._tokens == 0 28 | # refill tokens 29 | assert bucket.refill() is True 30 | # check tokens is 10 31 | assert bucket._tokens == 10 32 | 33 | 34 | def test_bucket_regen(): 35 | bucket = TokenBucket(10, 10) 36 | # success 37 | assert bucket.consume(10) is True 38 | # sleep 39 | time.sleep(1) 40 | # bucket should be full again and this should succeed 41 | assert bucket.tokens == 10 42 | assert bucket.consume(10) is True 43 | -------------------------------------------------------------------------------- /cloudbot/util/tokenbucket.py: -------------------------------------------------------------------------------- 1 | """ 2 | tokenbucket.py 3 | 4 | A python implementation of the token bucket algorithm. 5 | Adapted from 6 | 7 | Maintainer: 8 | - Luke Rogers 9 | 10 | License: 11 | Python Software Foundation License (PSF) 12 | """ 13 | 14 | from time import time 15 | 16 | 17 | class TokenBucket(object): 18 | """An implementation of the token bucket algorithm. 19 | >> bucket = TokenBucket(80, 0.5) 20 | >> bucket.consume(10) 21 | True 22 | >> bucket.consume(90) 23 | False 24 | """ 25 | 26 | def __init__(self, _capacity, fill_rate): 27 | """ 28 | :param _capacity: The total amount of token the bucket can contain 29 | :param fill_rate: The rate at which tokens regenerate. (fill_rate per second) 30 | """ 31 | """tokens is the total tokens in the bucket. fill_rate is the 32 | rate in tokens/second that the bucket will be refilled.""" 33 | self.capacity = float(_capacity) 34 | self._tokens = float(_capacity) 35 | self.fill_rate = float(fill_rate) 36 | self.timestamp = time() 37 | 38 | def consume(self, tokens): 39 | """ 40 | Consume tokens from the bucket. 41 | :param tokens: The number of tokens to consume 42 | :return true if there were sufficient tokens otherwise false 43 | """ 44 | if tokens <= self.tokens: 45 | self._tokens -= tokens 46 | else: 47 | return False 48 | return True 49 | 50 | def refill(self): 51 | """ 52 | Sets the current token count to the max capacity 53 | """ 54 | self._tokens = self.capacity 55 | return True 56 | 57 | def empty(self): 58 | """ 59 | Sets the current token count to zero 60 | """ 61 | self._tokens = float(0) 62 | return True 63 | 64 | def get_tokens(self): 65 | """ 66 | Calculates and returns the current amount of tokens the bucker contains 67 | 68 | :return Amount of tokens the bucket contains 69 | :rtype Float 70 | """ 71 | now = time() 72 | if self._tokens < self.capacity: 73 | delta = self.fill_rate * (now - self.timestamp) 74 | self._tokens = min(self.capacity, self._tokens + delta) 75 | self.timestamp = now 76 | return self._tokens 77 | 78 | tokens = property(get_tokens) 79 | -------------------------------------------------------------------------------- /data/8ball_responses.txt: -------------------------------------------------------------------------------- 1 | $(dark_green, bold)As I see it, yes 2 | $(dark_green, bold)It is certain 3 | $(dark_green, bold)It is decidedly so 4 | $(dark_green, bold)Most likely 5 | $(dark_green, bold)Outlook good 6 | $(dark_green, bold)Signs point to yes 7 | $(dark_green, bold)One would be wise to think so 8 | $(dark_green, bold)Naturally 9 | $(dark_green, bold)Without a doubt 10 | $(dark_green, bold)Yes 11 | $(dark_green, bold)Yes, definitely 12 | $(dark_green, bold)You may rely on it 13 | $(bold)Reply hazy, try again 14 | $(bold)Ask again later 15 | $(bold)Better not tell you now 16 | $(bold)Cannot predict now 17 | $(bold)Concentrate and ask again 18 | $(bold)You know the answer better than I 19 | $(bold)Maybe... 20 | $(dark_red, bold)You're kidding, right? 21 | $(dark_red, bold)Don't count on it 22 | $(dark_red, bold)In your dreams 23 | $(dark_red, bold)My reply is no 24 | $(dark_red, bold)My sources say no 25 | $(dark_red, bold)Outlook not so good 26 | $(dark_red, bold)Very doubtful 27 | -------------------------------------------------------------------------------- /data/attacks/bite.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{action} {user}'s {bodypart}" 4 | ], 5 | "parts": { 6 | "action": [ 7 | "bites", 8 | "nips", 9 | "nibbles", 10 | "chomps", 11 | "licks", 12 | "teases", 13 | "chews", 14 | "gums", 15 | "tastes" 16 | ], 17 | "bodypart": [ 18 | "cheeks", 19 | "ear lobes", 20 | "nipples", 21 | "nose", 22 | "neck", 23 | "toes", 24 | "fingers", 25 | "butt", 26 | "taint", 27 | "thigh", 28 | "grundle", 29 | "tongue", 30 | "calf", 31 | "nurses", 32 | "nape" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /data/attacks/clinton.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "thinks {user} is {insult}", 4 | "thinks that {user} helped Clinton delete her emails", 5 | "knows that {user} assisted Clinton with Benghazi", 6 | "saw {user} help Clinton when she collapsed", 7 | "tells {user} to Pok\u00e9mon GO to the polls", 8 | "did not have sexual relations with {user}", 9 | "buys {user} a blue dress" 10 | ], 11 | "parts": { 12 | "insult": [ 13 | "deplorable", 14 | "a basement dweller" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /data/attacks/compliment.json: -------------------------------------------------------------------------------- 1 | { 2 | "target_templates": [ 3 | "{user}, {phrase}." 4 | ], 5 | "parts": { 6 | "phrase": [ 7 | "you are like a spring flower; beautiful and vivacious", 8 | "you smell nice", 9 | "you have very nice teeth", 10 | "I like your pants", 11 | "you have a wonderful face", 12 | "you've got a nice butt", 13 | "I am utterly disarmed by your wit", 14 | "your beauty is why poetry was invented", 15 | "your eyes shine like the sun", 16 | "your skin is as soft as Froyo", 17 | "how does your head hold such a big brain" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /data/attacks/fight.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{bang}! {bang}! {bang}! {nick} {victory} over {user} with a {blow_type} {blow}.", 4 | "{bang}! {bang}! {bang}! {user} {victory} over {nick} with a {blow_type} {blow}." 5 | ], 6 | "parts": { 7 | "bang": [ 8 | "BANG", 9 | "POW", 10 | "SLAM", 11 | "WHACK", 12 | "SLAP", 13 | "KAPOW", 14 | "ZAM", 15 | "BOOM" 16 | ], 17 | "blow_type": [ 18 | "devastating", 19 | "destructive", 20 | "ruthless", 21 | "damaging", 22 | "ruinous", 23 | "catastrophic", 24 | "traumatic", 25 | "shattering", 26 | "overwhelming", 27 | "crushing", 28 | "fierce", 29 | "deadly", 30 | "lethal", 31 | "fatal", 32 | "savage", 33 | "violent" 34 | ], 35 | "victory": [ 36 | "wins", 37 | "stands victorious", 38 | "triumphs", 39 | "conquers", 40 | "is the champion", 41 | "is the victor" 42 | ], 43 | "blow": [ 44 | "uppercut", 45 | "hammerfist", 46 | "elbow strike", 47 | "shoulder strike", 48 | "front kick", 49 | "side kick", 50 | "roundhouse kick", 51 | "knee strike", 52 | "butt strike", 53 | "headbutt", 54 | "haymaker punch", 55 | "palm strike", 56 | "pocket bees" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /data/attacks/glomp.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{action} {user}." 4 | ], 5 | "parts": { 6 | "action": [ 7 | "glomps", 8 | "tackles", 9 | "tackle hugs", 10 | "sexually glomps", 11 | "takes a flying leap and glomps", 12 | "bear hugs" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /data/attacks/highfive.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{nick} tries to give {user} a five up high but misses. that was awkward", 4 | "{nick} gives {user} a killer high-five", 5 | "{nick} gives {user} an elbow-shattering high-five", 6 | "{nick} smashes {user} up high", 7 | "{nick} slaps skin with {user}", 8 | "{nick} {user} winds up for a killer five but misses and falls flat on his face", 9 | "{nick} halfheartedly high-fives {user}", 10 | "{nick} gives {user} a smooth five down low", 11 | "{nick} gives {user} a friendly high five", 12 | "{nick} starts to give {user} a high five, but leaves them hanging", 13 | "{nick} performs an incomprehensible handshake with {user} that identifies them as the very best of friends", 14 | "{nick} makes as if to high five {user} but pulls his hand away at the last second", 15 | "{nick} leaves {user} hanging", 16 | "{nick} offers a fist and {user} pounds it" 17 | ], 18 | "parts": {} 19 | } 20 | -------------------------------------------------------------------------------- /data/attacks/hug.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": {}, 3 | "templates": [ 4 | "{nick} wraps arms around {target} and clings forever", 5 | "{nick} gives {target} a BIIIIIIIIG hug!!!", 6 | "{nick} gives {target} a warming hug", 7 | "{nick} hugs {target} into a coma", 8 | "{nick} squeezes {target} to death", 9 | "{nick} gives {target} a christian side hug", 10 | "{nick} glomps {target}", 11 | "{nick} reluctantly hugs {target}...", 12 | "{nick} gives {target} a well-deserved hug :)", 13 | "{nick} hugs {target}", 14 | "{nick} hugs {target} forever and ever and ever", 15 | "cant stop, wont stop. {nick} hugs {target} until the sun goes cold", 16 | "{nick} rallies up everyone in the channel to give {target} a group hug", 17 | "{nick} gives {target} a tight hug and rubs their back", 18 | "{nick} hugs {target} and gives their hair a sniff", 19 | "{nick} smothers {target} with a loving hug" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /data/attacks/lurve.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{nick} wraps arms around {target} and clings forever", 4 | "{nick} cuddles {target} in the fluffiest blanket ever", 5 | "{nick} lays their head on the lap of {target} and goes to sleep, dreaming da best sweet dreams", 6 | "{nick} caresses {target}'s hair", 7 | "{nick} caresses {target}'s cheek", 8 | "{nick} plants a shy kiss on {target}'s cheek", 9 | "{nick} gives {target} a BIIIIIIIIG hug!!!", 10 | "{nick} lovingly tackles {target} into a pit of the softest pillows ever", 11 | "{nick} cheers happily for {target}!!", 12 | "{nick} pulls {target} back into bed for more cuddles \u2665~", 13 | "{nick} snuggles {target} for Netflix and chili popcorn", 14 | "{nick} happily kisses {target} on the cheek", 15 | "{nick} shares a milkshake with {target}" 16 | ], 17 | "parts": {} 18 | } 19 | -------------------------------------------------------------------------------- /data/attacks/present.json: -------------------------------------------------------------------------------- 1 | { 2 | "target_templates": [ 3 | "hands {user} a {gift}" 4 | ], 5 | "parts": { 6 | "gift": [ 7 | "Lighter", 8 | "VCR", 9 | "Video Game", 10 | "Blu-Ray Player", 11 | "Red Rider BB Gun", 12 | "Coal", 13 | "Chocolate", 14 | "Socks", 15 | "Necklace", 16 | "Watch", 17 | "20 dollars", 18 | "100 dollars", 19 | "50 dollars", 20 | "5 dollars", 21 | "lego technic set", 22 | "Makeup", 23 | "Gloves", 24 | "Tv", 25 | "PS4", 26 | "Amazon Gift Card", 27 | "Wii", 28 | "X-Box", 29 | "two front teeth", 30 | "DS", 31 | "Home Depot Gift Card", 32 | "Grocery store Gift Card", 33 | "Costco Gift Card", 34 | "Dollar Store Gift Card", 35 | "Mall Gift Card", 36 | "Spa Gift Card", 37 | "Computer", 38 | "Laptop", 39 | "Car", 40 | "Toaster", 41 | "Mixer", 42 | "BottleofBooze", 43 | "Beer", 44 | "Pen", 45 | "Notepad", 46 | "Dvd", 47 | "lotteryticket", 48 | "Blu-Ray" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/attacks/spank.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "grabs {user} and {spank} them {xtimes} times with {item}." 4 | ], 5 | "parts": { 6 | "spank": [ 7 | "paddles", 8 | "spanks", 9 | "whips", 10 | "lashes", 11 | "straps", 12 | "smacks", 13 | "wallops" 14 | ], 15 | "xtimes": [ 16 | "two", 17 | "three", 18 | "four", 19 | "five", 20 | "six", 21 | "seven", 22 | "eight", 23 | "nine", 24 | "ten" 25 | ], 26 | "item": [ 27 | "a riding crop", 28 | "a wooden spoon", 29 | "a leather flogger", 30 | "a wooden paddle", 31 | "a wooden cane", 32 | "a hickory tree switch", 33 | "a feather duster", 34 | "a cat o' nine tails", 35 | "a leather belt", 36 | "an open hand" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /data/attacks/strax.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "Might I suggest {type} {attack} with {preweapon} {postweapon} and {weapon}!" 4 | ], 5 | "target_templates": [ 6 | "Might I suggest {type} {attack} on {user} with {preweapon} {postweapon} and {weapon}!" 7 | ], 8 | "parts": { 9 | "type": [ 10 | "a full-frontal", 11 | "a pincer", 12 | "a surprise", 13 | "a brutally excessive", 14 | "a suicide", 15 | "a multi-pronged", 16 | "a glorious", 17 | "an acid-heavy", 18 | "an immediate", 19 | "a violent", 20 | "a traditional Sontaran", 21 | "a devasting" 22 | ], 23 | "attack": [ 24 | "assault", 25 | "attack", 26 | "bombardment", 27 | "offensive", 28 | "barrage", 29 | "charge", 30 | "strike", 31 | "operation", 32 | "manoeuvre" 33 | ], 34 | "preweapon": [ 35 | "laser", 36 | "berserker", 37 | "acid", 38 | "armoured attack", 39 | "proton", 40 | "three kinds of", 41 | "atomic", 42 | "toxic", 43 | "explosive", 44 | "red-hot", 45 | "thermal", 46 | "automated fire", 47 | "cluster", 48 | "enhanced germ", 49 | "energy-drink-fueled" 50 | ], 51 | "postweapon": [ 52 | "bees", 53 | "chainsaws", 54 | "marmots", 55 | "acid", 56 | "monkeys", 57 | "mines", 58 | "bombs", 59 | "snakes", 60 | "spiders", 61 | "knives", 62 | "rockets", 63 | "sharks", 64 | "owls", 65 | "repurposed cybermats", 66 | "cannons", 67 | "alligators", 68 | "scalpel mines" 69 | ], 70 | "weapon": [ 71 | "robots", 72 | "ninjas", 73 | "grenades", 74 | "a dolphin full of napalm", 75 | "acid", 76 | "dynamite", 77 | "xenomorphs", 78 | "lots and lots of C4", 79 | "tactical nukes", 80 | "MacGyver", 81 | "bio-weapons", 82 | "rocket launchers", 83 | "an elephant", 84 | "automated laser monkeys", 85 | "a memory worm for afterwards", 86 | "this pencil" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /data/attacks/trump.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "grabs {user} by the pussy.", 4 | "walks in on {user} while undressing.", 5 | "pops a tic-tac and kisses {user}.", 6 | "calls {user} {insult}.", 7 | "makes the wall 10 feet higher and makes {user} pay for it.", 8 | "thinks {user} isn't sending their best.", 9 | "thinks {user} is great. the greatest. a really great user.", 10 | "thinks {user} has a yuge problem", 11 | "thinks people love {user}. And you know what, {user} has been very successful. Everybody loves {user}.", 12 | "thinks {user} actually doesn't have a bad hairline.", 13 | "makes {user} great again.", 14 | "knows that {user} had someone killed to keep them quiet.", 15 | "saw {user} rig the DNC primaries in favor of Clinton." 16 | ], 17 | "parts": { 18 | "insult": [ 19 | "a huge disaster", 20 | "crooked", 21 | "a nasty woman", 22 | "a bad hombre", 23 | "dumb as a rock", 24 | "very sad", 25 | "a total hypocrite", 26 | "not a nice person", 27 | "a dope", 28 | "a total failure", 29 | "not big league" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /data/cheers.txt: -------------------------------------------------------------------------------- 1 | FUCK YEAH! 2 | HOORAH! 3 | HURRAY! 4 | OORAH! 5 | YAY! 6 | *\o/* CHEERS! *\o/* 7 | HOOHAH! 8 | HOOYAH! 9 | HUAH! 10 | ♪ ┏(°.°)┛ ┗(°.°)┓ ♬ 11 | -------------------------------------------------------------------------------- /data/foaas.json: -------------------------------------------------------------------------------- 1 | { 2 | "fuck_offs": [ 3 | "donut", 4 | "bus", 5 | "chainsaw", 6 | "king", 7 | "madison", 8 | "gfy", 9 | "back", 10 | "keep", 11 | "name", 12 | "bday", 13 | "dalton", 14 | "ing", 15 | "nugget", 16 | "outside", 17 | "off", 18 | "problem", 19 | "shakespeare", 20 | "think", 21 | "thinking", 22 | "xmas", 23 | "yoda", 24 | "you" 25 | ], 26 | "single_fucks": [ 27 | "bag", 28 | "awesome", 29 | "because", 30 | "bucket", 31 | "bye", 32 | "cool", 33 | "everyone", 34 | "everything", 35 | "flying", 36 | "give", 37 | "horse", 38 | "life", 39 | "looking", 40 | "maybe", 41 | "me", 42 | "mornin", 43 | "no", 44 | "pink", 45 | "retard", 46 | "rtfm", 47 | "sake", 48 | "shit", 49 | "single", 50 | "thanks", 51 | "that", 52 | "this", 53 | "too", 54 | "tucker", 55 | "zayn", 56 | "zero" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /data/food/cake.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{method} {user} a {flavor} {size} {type} cake and serves it with a small {side}!" 4 | ], 5 | "parts": { 6 | "method": [ 7 | "makes", 8 | "gives", 9 | "gets", 10 | "buys" 11 | ], 12 | "flavor": [ 13 | "tasty", 14 | "delectable", 15 | "delicious", 16 | "yummy", 17 | "toothsome", 18 | "scrumptious", 19 | "luscious" 20 | ], 21 | "size": [ 22 | "small", 23 | "little", 24 | "mid-sized", 25 | "medium-sized", 26 | "large", 27 | "gigantic" 28 | ], 29 | "type": [ 30 | "Chocolate", 31 | "Ice Cream", 32 | "Angel", 33 | "Boston Cream", 34 | "Birthday", 35 | "Bundt", 36 | "Carrot", 37 | "Coffee", 38 | "Devils", 39 | "Fruit", 40 | "Gingerbread", 41 | "Pound", 42 | "Red Velvet", 43 | "Stack", 44 | "Welsh", 45 | "Yokan" 46 | ], 47 | "side": [ 48 | "glass of chocolate milk", 49 | "bowl of ice cream", 50 | "jar of cookies", 51 | "side of chocolate sauce" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /data/food/cheese.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {size} {type} of {cheese}." 4 | ], 5 | "parts": { 6 | "size": [ 7 | "small", 8 | "medium", 9 | "large", 10 | "double", 11 | "half-eaten", 12 | "semi-chewed on", 13 | "generously big", 14 | "enormous", 15 | "comically tiny", 16 | "stolen", 17 | "smuggled", 18 | "confiscated", 19 | "rare", 20 | "half off at the market", 21 | "Deli's special" 22 | ], 23 | "type": [ 24 | "slice", 25 | "serving", 26 | "slab", 27 | "wheel", 28 | "crate", 29 | "crumb", 30 | "morsel", 31 | "brick", 32 | "ball", 33 | "tube", 34 | "tub", 35 | "jar", 36 | "bath tub", 37 | "warehouse", 38 | "freight car" 39 | ], 40 | "cheese": [ 41 | "KoKo\u2019s coconut gouda", 42 | "Chocolate stout cheddar", 43 | "White stilton with mango and ginger", 44 | "Baked brie with jam", 45 | "Baked brie with roasted garlic and walnuts", 46 | "Lambchopper", 47 | "Smokey goat cheese", 48 | "Velveeta", 49 | "Smoked halloumi", 50 | "Aged Gruyere", 51 | "Camembert De Normandie", 52 | "Leerdamer", 53 | "Pesto Gouda", 54 | "Pepper Jack cheese", 55 | "Cranberry cheddar", 56 | "Mozzarella", 57 | "Feta cheese", 58 | "Stilton", 59 | "Bavarian smoked cheese", 60 | "Veiny gorgonzola", 61 | "Dill havarti", 62 | "Tillamook pepper jack cheese", 63 | "Drunken gouda", 64 | "Drunken goat", 65 | "Habanero cheddar", 66 | "Red Leicester", 67 | "Grana-Pandano", 68 | "Emmentaler", 69 | "Camembert", 70 | "Provolone ", 71 | "Gruyere", 72 | "Parmesan cheese ", 73 | "Roquefort cheese", 74 | "Sharp cheddar", 75 | "Raclette cheese", 76 | "Mortadella cheese", 77 | "Ricotta", 78 | "Mascarpone", 79 | "Cream Cheese", 80 | "Taleggio", 81 | "head cheese" 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /data/food/coffee.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {size} {temperature} {flavor} {coffee} with {shots} of espresso!" 4 | ], 5 | "parts": { 6 | "temperature": [ 7 | "hot", 8 | "cold", 9 | "iced", 10 | "room temperature", 11 | "lukewarm" 12 | ], 13 | "size": [ 14 | "large", 15 | "medium", 16 | "small", 17 | "grande", 18 | "venti", 19 | "tall" 20 | ], 21 | "flavor": [ 22 | "vanilla", 23 | "caramel", 24 | "white chocolate", 25 | "hazelnut", 26 | "cinnamon" 27 | ], 28 | "coffee": [ 29 | "americano", 30 | "cubano", 31 | "cappuccino", 32 | "latte", 33 | "mocha", 34 | "macchiato", 35 | "breve" 36 | ], 37 | "shots": [ 38 | "one shot", 39 | "two shots", 40 | "three shots" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /data/food/cookies.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{method} {user} a {flavor} {size} {type} cookie and serves it with a {side}!" 4 | ], 5 | "parts": { 6 | "method": [ 7 | "makes", 8 | "gives", 9 | "gets", 10 | "buys" 11 | ], 12 | "flavor": [ 13 | "tasty", 14 | "delectable", 15 | "delicious", 16 | "yummy", 17 | "toothsome", 18 | "scrumptious", 19 | "luscious" 20 | ], 21 | "size": [ 22 | "small", 23 | "little", 24 | "medium-sized", 25 | "large", 26 | "gigantic" 27 | ], 28 | "type": [ 29 | "Chocolate Chip", 30 | "Oatmeal", 31 | "Sugar", 32 | "Oatmeal Raisin", 33 | "Macadamia Nut", 34 | "Jam Thumbprint", 35 | "Mexican Wedding", 36 | "Biscotti", 37 | "Oatmeal Cranberry", 38 | "Chocolate Fudge", 39 | "Peanut Butter", 40 | "Pumpkin", 41 | "Lemon Bar", 42 | "Chocolate Oatmeal Fudge", 43 | "Toffee Peanut", 44 | "Danish Sugar", 45 | "Tim Tam", 46 | "Triple Chocolate", 47 | "Oreo" 48 | ], 49 | "side": [ 50 | "glass of milk", 51 | "bowl of ice cream", 52 | "bowl of chocolate sauce" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /data/food/donut.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {donut} donut." 4 | ], 5 | "parts": { 6 | "donut": [ 7 | "chocolate long john", 8 | "vanilla long john", 9 | "bear claw", 10 | "old fashioned", 11 | "Boston cream", 12 | "jelly", 13 | "chocolate cake", 14 | "glazed", 15 | "strawberry frosted with sprinkles", 16 | "chocolate frosted with sprinkles", 17 | "vanilla frosted with sprinkles", 18 | "blueberry cake", 19 | "cinnamon", 20 | "sugared", 21 | "assorted holes", 22 | "powdered", 23 | "apple fritter", 24 | "double chocolate", 25 | "maple glaze" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data/food/doobie.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "passes {user} a {size} {adjective} {doobie} made with the finest {strain}" 4 | ], 5 | "parts": { 6 | "adjective": [ 7 | "perfectly-rolled", 8 | "sweet-smelling", 9 | "dank, dank", 10 | "killer", 11 | "cherried", 12 | "world record breaking", 13 | "glorious", 14 | "unfathomable", 15 | "awe-inspiring", 16 | "spine-chilling" 17 | ], 18 | "size": [ 19 | "footlong", 20 | "medium", 21 | "snoopworthy", 22 | "pathetically-sized", 23 | "one ounce", 24 | "regular-sized", 25 | "giant", 26 | "super size", 27 | "king size", 28 | "1 and 1/4" 29 | ], 30 | "doobie": [ 31 | "doobie", 32 | "spliff", 33 | "joint", 34 | "blunt", 35 | "reefer" 36 | ], 37 | "strain": [ 38 | "Blue Dream", 39 | "Sour Diesel", 40 | "OG Kush", 41 | "Shitty UK Cheese", 42 | "Grandy Purp", 43 | "White Widow", 44 | "Pineapple Express", 45 | "Girl Scout Cookies", 46 | "Strawberry Banana", 47 | "Dutch Treat", 48 | "BlueBerry Muffin", 49 | "Green Crack", 50 | "Sour Diesel", 51 | "Paris OG", 52 | "Tangie", 53 | "Sour Tangie", 54 | "BlueBerry Headband", 55 | "Larry Bird OG", 56 | "Pineapple Chunk", 57 | "Gorilla Glue #4", 58 | "Durban Poision" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /data/food/halal.json: -------------------------------------------------------------------------------- 1 | { 2 | "target_templates": [ 3 | "Serves {target} {quantity} {quality} {dish}" 4 | ], 5 | "templates": [ 6 | "has {quantity} {quality} {dish}" 7 | ], 8 | "parts": { 9 | "quantity": [ 10 | "a little bit of", 11 | "a heaping pile of", 12 | "a moderate serving of", 13 | "a taste of", 14 | "just a smell of" 15 | ], 16 | "quality": [ 17 | "fresh made", 18 | "left over", 19 | "just out of the oven" 20 | ], 21 | "dish": [ 22 | "Rice and Goat Meat", 23 | "Goat Curry", 24 | "Hummus bi Tahina", 25 | "L\u00e4ghm\u00e4n", 26 | "Mutton biryani", 27 | "Kabuli palao", 28 | "Shakshouka", 29 | "Mutton Msala", 30 | "Fatteh Betnjan", 31 | "Caprese stuffed chicken breast", 32 | "Maqloobeh", 33 | "Koofteh berenji", 34 | "Fish Makkanwala", 35 | "Szechwan" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/food/kebab.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a delicious {kebab} with {side} and {sauce}" 4 | ], 5 | "parts": { 6 | "kebab": [ 7 | "lamb doner kebab", 8 | "chicken doner kebab", 9 | "shawarma" 10 | ], 11 | "side": [ 12 | "salad", 13 | "fries" 14 | ], 15 | "sauce": [ 16 | "garlic sauce", 17 | "chili sauce", 18 | "mayonnaise", 19 | "ketchup", 20 | "BBQ sauce", 21 | "mint sauce", 22 | "tzatziki sauce", 23 | "no sauce" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /data/food/keto.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {meal}." 4 | ], 5 | "parts": { 6 | "meal": [ 7 | "bacon, red pepper, and mozzarella frittatta", 8 | "crockpot buffalo chicken soup", 9 | "ginger sesame glazed salmon", 10 | "egg salad stuffed avocado", 11 | "bacon cheeseburger soup", 12 | "pork carne asada taco", 13 | "stuffed poblano pepper", 14 | "keto coffee cake", 15 | "almond butter chia square", 16 | "caprese salad", 17 | "bacon wrapped asparagus", 18 | "Breakfast keto waffles", 19 | "Jalapeno popper egg cups", 20 | "Bacon cheddar chive omelette", 21 | "Mini keto pancake donuts", 22 | "Avocado tuna melt bites", 23 | "Cheese stuffed bacon wrapped hot dogs", 24 | "5 minute keto egg drop soup", 25 | "Crispy tofu and bok choy salad", 26 | "Keto coconut curry chicken tenders", 27 | "Asian grilled keto short ribs", 28 | "Cheese stuffed bacon cheeseburger", 29 | "Perfectly crisp baked chicken wings", 30 | "No bake chocolate peanut butter fat bombs", 31 | "Keto tortilla chips", 32 | "Neapolitan fat bombs", 33 | "Coconut orange creamsicle fat bombs", 34 | "Reverse seared ribeye steak", 35 | "Cauliflower base pepperoni pizza", 36 | "Bacon and bacon and bacon and more bacon" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /data/food/muffin.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {muffin} muffin." 4 | ], 5 | "parts": { 6 | "muffin": [ 7 | "blueberry", 8 | "lemon poppyseed", 9 | "chocolate", 10 | "chocolate chip", 11 | "orange cranberry zest", 12 | "corn", 13 | "pumpkin", 14 | "banana nut", 15 | "apple cinnamon" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/food/noodles.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {noodle} served with {side} and {side}" 4 | ], 5 | "parts": { 6 | "noodle": [ 7 | "spicy bowl of mie goreng fried noodles", 8 | "hot bowl of pho noodle soup", 9 | "bowl of pork and mushroom wonton noodle soup", 10 | "plate of pan fried rice noodles with sliced beef", 11 | "plate of glass noodles with seasonal vegetables", 12 | "bowl of vegetable yakisoba", 13 | "bowl of vermicelli noodles with lemongrass chicken and spicy fish sauce", 14 | "plate of pancit luglug topped with hardboiled eggs, shrimp, and chorizo", 15 | "bowl of pancit malabon with freshly caught seafood", 16 | "hot bowl of chicken noodle soup", 17 | "extra large bowl of saimin with cabbage", 18 | "whole wok of Singapore-style noodles with extra curry flavor", 19 | "plate of mildly spicy drunken noodles", 20 | "steaming bowl of dandan noodles topped with peanut butter", 21 | "takeout box of Shanghai-style noodles with extra choi", 22 | "generous bowl of b\u00fan b\u00f2 hu\u1ebf", 23 | "hot bowl of curry laksa with tofu puffs and cuttlefish", 24 | "large portion of bibim guksu", 25 | "steaming bowl of tonkatsu ramen", 26 | "pot of shirataki noodles", 27 | "small bowl of s\u014dmen salad", 28 | "plate of classic spaghetti with meatballs" 29 | ], 30 | "side": [ 31 | "a poached egg", 32 | "a sunny side up egg", 33 | "shredded chicken", 34 | "shredded pork", 35 | "a pork cutlet", 36 | "packaged crackers", 37 | "pickled vegetables", 38 | "some grilled lemongrass-rubbed fish", 39 | "roasted seaweed", 40 | "panko-crusted deep fried prawns", 41 | "extra garlicky toasted bread", 42 | "a side of kimchi", 43 | "deep fried tofu", 44 | "a side of sliced beef brisket", 45 | "a side of peppery fried chicken nuggets", 46 | "a side of spring rolls", 47 | "a side of salad rolls", 48 | "a side of gyoza dumplings" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/food/rice.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a bowl of {rice} rice." 4 | ], 5 | "parts": { 6 | "rice": [ 7 | "Uncle Ben's Basmati", 8 | "Uncle Ben's Golden Vegetable ", 9 | "Japanese Koshihikari", 10 | "Camargue red", 11 | "Brown", 12 | "Wehani", 13 | "Jasmine", 14 | "Sticky", 15 | "Hokkien fried", 16 | "Bibimbap", 17 | "Dal bhat", 18 | "Onigiri", 19 | "Long-grain Nychaki", 20 | "Iranian Domsiah", 21 | "Italian Vialone Nano", 22 | "N\u00e0ng Th\u01a1m Ch\u1ee3 \u0110\u00e0o", 23 | "Htaman\u00e8", 24 | "Aromic Tulaipanji" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/food/sandwich.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{gives} {user} {flavor} {meat} and {meat} {type} with {salad} and {sauce}!", 4 | "{gives} {user} {flavor} {meat} and {meat} {type} with {salad}, {salad}, and {sauce}!", 5 | "{gives} {user} {flavor} {meat} and {meat} {type} with {salad}, {salad}, {salad}, and {sauce}!", 6 | "{gives} {user} {flavor} {meat} {type} with {salad} and {sauce}!", 7 | "{gives} {user} {flavor} {meat} {type} with {salad}, {salad}, and {sauce}!", 8 | "{gives} {user} {flavor} {meat} {type} with {salad}, {salad}, {salad}, and {sauce}!" 9 | ], 10 | "parts": { 11 | "gives": [ 12 | "hands", 13 | "gives", 14 | "makes", 15 | "passes" 16 | ], 17 | "flavor": [ 18 | "a tasty", 19 | "a delicious", 20 | "an awesome", 21 | "an excellent", 22 | "a beautifully-made" 23 | ], 24 | "meat": [ 25 | "ham", 26 | "salami", 27 | "steak", 28 | "meatball", 29 | "tuna", 30 | "pork", 31 | "chicken", 32 | "roast beef", 33 | "corned beef", 34 | "pastrami", 35 | "turkey" 36 | ], 37 | "type": [ 38 | "roll", 39 | "wrap", 40 | "pita", 41 | "sandwich", 42 | "bun" 43 | ], 44 | "salad": [ 45 | "tomatoes", 46 | "lettuce", 47 | "pickles", 48 | "cucumbers", 49 | "red onions", 50 | "jalapenos" 51 | ], 52 | "sauce": [ 53 | "sweet onion sauce", 54 | "honey mustard sauce", 55 | "mayo", 56 | "sweet chili sauce", 57 | "italian sauce", 58 | "ranch dressing", 59 | "barbecue sauce" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /data/food/scone.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {scone} scone." 4 | ], 5 | "parts": { 6 | "scone": [ 7 | "almond", 8 | "apple brie", 9 | "apricot ", 10 | "blueberry", 11 | "blackberry", 12 | "pumpkin", 13 | "chocolate and orange", 14 | "chocolate", 15 | "cinnamon sugar", 16 | "coconut", 17 | "cranberry", 18 | "cranberry lime", 19 | "lemon glazed", 20 | "maple bacon brown sugar", 21 | "Nutella", 22 | "pear and goat cheese", 23 | "strawberry", 24 | "cheddar and roasted red pepper", 25 | "lemon poppyseed", 26 | "vanilla bean", 27 | "cherry almond", 28 | "pesto and sun-dried tomato", 29 | "raspberry", 30 | "lavender and honey", 31 | "orange and cranberry", 32 | "oatmeal pecan", 33 | "cheddar and rosemary", 34 | "bacon and gruyere", 35 | "black currant", 36 | "potato" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /data/food/soup.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {size} {soup} soup with {side}." 4 | ], 5 | "parts": { 6 | "soup": [ 7 | "Ajiaco", 8 | "Avgolemono", 9 | "Borscht", 10 | "Beef noodle", 11 | "Beer", 12 | "Birds nest", 13 | "Chicken noodle", 14 | "Caldo verde", 15 | "Cazuela", 16 | "Chicken Noodle", 17 | "Cock-a-leekie", 18 | "Cream of crab", 19 | "Fufu and Egusi", 20 | "Gomguk", 21 | "Goulash", 22 | "Gumbo", 23 | "Kharcho", 24 | "Kimchi Guk", 25 | "Lagman", 26 | "Leek", 27 | "Lentil", 28 | "Maryland crab", 29 | "Matzah ball", 30 | "Menudo", 31 | "Minestrone", 32 | "Miyeok guk", 33 | "Milligatawny", 34 | "Barley", 35 | "Nettle", 36 | "Oxtail", 37 | "Pozole", 38 | "Pumpkin", 39 | "Samgyetang", 40 | "Snert", 41 | "Corn chowder", 42 | "French onion", 43 | "Lobster", 44 | "Miso", 45 | "She-crab", 46 | "Tomato", 47 | "Tteokguk", 48 | "Winter mellon", 49 | "Crab Gaxpacho", 50 | "Salmorejo", 51 | "Tarator", 52 | "New England clam chowder" 53 | ], 54 | "size": [ 55 | "Large bowl of", 56 | "Small bowl of", 57 | "Cup of", 58 | "Gallon of", 59 | "Medium bowl of", 60 | "Bread bowl of" 61 | ], 62 | "side": [ 63 | "Some Croutons", 64 | "A baguette", 65 | "Sliced apples", 66 | "Chicken" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /data/food/sushi.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "gives {user} a {sushi} roll {sushi_extra}" 4 | ], 5 | "parts": { 6 | "sushi": [ 7 | "Kappa Maki", 8 | "California", 9 | "Philadelphia", 10 | "Hamachi Yellow Tail", 11 | "Tamagoyaki fried egg", 12 | "Futomaki", 13 | "Natto Maki Soy Bean", 14 | "Negitoro Blue Fin", 15 | "Kamaboko Kani Crab", 16 | "Philadelphia", 17 | "Crab meat", 18 | "Raw salmon", 19 | "Raw tuna", 20 | "Eel", 21 | "Seared salmon", 22 | "Seared Ahi Tuna", 23 | "Shark", 24 | "Shrimp tempura", 25 | "Dragon" 26 | ], 27 | "sushi_extra": [ 28 | "drizzled with eel sauce", 29 | "garnished with lemon", 30 | "decorated with cilantro", 31 | "topped with a line of sriracha aeoli", 32 | "prepared with love", 33 | "made to the standards of the first emperor of Japan", 34 | "rolled to perfection", 35 | "with a bit of wasabi", 36 | "decorated with little cucumber slices", 37 | "so good master banishes him from his temple", 38 | "so amazing the emperor appoints him as Chief Sushi Chef", 39 | "forged of iron!", 40 | "with the freshest seaweed off the coasts of Japan", 41 | "delicately crafted with mastery", 42 | "that shows the true skill of the sushi chef", 43 | "with flavor subtle like the ninja warriors of Kyoto" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /data/food/taco.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {quality} {type} taco filled with {meat} and topped with {topping}, {topping} and {topping}!" 4 | ], 5 | "parts": { 6 | "type": [ 7 | "hard-shell", 8 | "soft-shell", 9 | [ 10 | "crispy", 11 | 1 12 | ], 13 | [ 14 | "puffy", 15 | 1 16 | ], 17 | [ 18 | "Indian", 19 | 1 20 | ] 21 | ], 22 | "quality": [ 23 | "spicy", 24 | "mild", 25 | "delicious", 26 | [ 27 | "boring", 28 | 1 29 | ], 30 | [ 31 | "disgusting", 32 | 1 33 | ], 34 | "perfect" 35 | ], 36 | "meat": [ 37 | "minced beef", 38 | "shredded beef", 39 | "steak", 40 | "pork", 41 | "various meats", 42 | "chicken", 43 | "refried beans", 44 | [ 45 | "tofu", 46 | 1 47 | ] 48 | ], 49 | "topping": [ 50 | [ 51 | "guacamole", 52 | 10 53 | ], 54 | [ 55 | "salsa", 56 | 10 57 | ], 58 | [ 59 | "sour cream", 60 | 10 61 | ], 62 | "cheese", 63 | "lettuce", 64 | "tomatoes", 65 | "avocado", 66 | "onion", 67 | "scallions", 68 | "jalape\u00f1os", 69 | "capsicum", 70 | [ 71 | "ghost chili", 72 | 1 73 | ], 74 | [ 75 | "olives", 76 | 1 77 | ], 78 | [ 79 | "pineapple", 80 | 1 81 | ], 82 | [ 83 | "raspberries", 84 | 1 85 | ] 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /data/food/tea.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "hands {user} a {size} {temperature} {tea} tea!" 4 | ], 5 | "parts": { 6 | "temperature": [ 7 | "hot", 8 | "room temperature", 9 | "lukewarm", 10 | "steaming" 11 | ], 12 | "size": [ 13 | "large", 14 | "medium", 15 | "small" 16 | ], 17 | "tea": [ 18 | "chai", 19 | "english breakfast", 20 | "Earl Grey", 21 | "sleepytime", 22 | "oolong", 23 | "green", 24 | "yerba mate", 25 | "rooibos", 26 | "bubble", 27 | "thai" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /data/hookup.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | "{user1} used {weapon} and did it with {user2} in the {room}." 4 | ], 5 | "parts": { 6 | "weapon": [ 7 | "a candlestick", 8 | "an axe", 9 | "a pistol", 10 | "rope", 11 | "gloves", 12 | "a horseshoe", 13 | "a knife", 14 | "a baseball bat", 15 | "a chalice", 16 | "a dumbbell", 17 | "a wrench", 18 | "a trophy", 19 | "a pipe", 20 | "garden shears" 21 | ], 22 | "room": [ 23 | "courtyard", 24 | "guest house", 25 | "observatory", 26 | "theatre", 27 | "drawing room", 28 | "garage", 29 | "spa", 30 | "master bedroom", 31 | "studio", 32 | "pool", 33 | "arcade", 34 | "beach house", 35 | "surf shop", 36 | "kitchen", 37 | "ballroom", 38 | "conservatory", 39 | "billiard room", 40 | "library", 41 | "study", 42 | "hallway", 43 | "lounge", 44 | "dining room", 45 | "cellar" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /data/lenny.json: -------------------------------------------------------------------------------- 1 | { 2 | "lenny": [ 3 | "( \u0361\u00b0 \u035c\u0296 \u0361\u00b0)", 4 | "( \u0360\u00b0 \u035f\u0296 \u0361\u00b0)", 5 | "\u1566( \u0361\u00b0 \u035c\u0296 \u0361\u00b0)\u1564", 6 | "( \u0361\u00b0 \u035c\u0296 \u0361\u00b0)", 7 | "( \u0361~ \u035c\u0296 \u0361\u00b0)", 8 | "( \u0361o \u035c\u0296 \u0361o)", 9 | "\u0361\u00b0 \u035c\u0296 \u0361 -", 10 | "( \u0361\u0361 \u00b0 \u035c \u0296 \u0361 \u00b0)\ufeff", 11 | "( \u0361 \u0361\u00b0 \u0361\u00b0 \u0296 \u0361\u00b0 \u0361\u00b0)", 12 | "(\u0e07 \u0360\u00b0 \u035f\u0644\u035c \u0361\u00b0)\u0e07", 13 | "( \u0361\u00b0 \u035c\u0296 \u0361 \u00b0)", 14 | "( \u0361\u00b0\u256d\u035c\u0296\u256e\u0361\u00b0 )" 15 | ], 16 | "flenny": [ 17 | "( \u0361\u00b0 \u035c \u0361\u00b0 )", 18 | "( \u0361\u00b0 \u035c \u0361\u00b0 )", 19 | "(\u0e07 \u0360\u00b0 \u035f \u0361\u00b0 )\u0e07", 20 | "( \u0361\u00b0_ \u0361\u00b0 )", 21 | "(\ufffd \u0361\u00b0 \u035c \u0361\u00b0 )\ufffd", 22 | "( \u25d5 \u035c \u25d5 )", 23 | "( \u0361~ \u035c \u0361\u00b0 )", 24 | "( \u0360\u00b0 \u035f \u0361\u00b0 )", 25 | "( \u0ca0 \u035c \u0ca0 )", 26 | "( \u0ca5 \u035c \u0ca5 )", 27 | "( \u0361^ \u035c \u0361^ )", 28 | "( \u0ca5 _ \u0ca5 )", 29 | "( \u0361\u00b0 \uff0d \u0361\u00b0 )", 30 | "\u2570( \u0361\u00b0 \u035c \u0361\u00b0)\u2283\u2501\u2606\u309c\u30fb\u3002\u3002\u30fb\u309c\u309c\u30fb\u3002\u3002\u30fb\u309c\u2606\u309c\u30fb\u3002\u3002\u30fb\u309c\u309c\u30fb\u3002\u3002\u30fb\u309c", 31 | "\u2534\u252c\u2534\u252c\u2534\u2524( \u0361\u00b0 \u035c \u251c\u252c\u2534\u252c\u2534\u252c", 32 | "( \u2310\u25a0 \u035c \u25a0 )", 33 | "( \u0361~ _ \u0361~ )", 34 | "@=( \u0361\u00b0 \u035c \u0361\u00b0 @ )\u2261", 35 | "( \u0361\u00b0\u06a1 \u0361\u00b0 )", 36 | "( \u2716_\u2716 )", 37 | "(\u3065 \u0361\u00b0 \u035c \u0361\u00b0 )\u3065", 38 | "\u10da( \u0361\u00b0 \u035c \u0361\u00b0 \u10da)", 39 | "( \u25c9 \u035c \u0361\u25d4 )" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /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 | } 55 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data/name_files/hobbits.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tolkien hobbit names", 3 | "author": "Johan Danforth", 4 | "templates": { 5 | "default": "{first}{mid}{final}" 6 | }, 7 | "default_templates": [ 8 | "default" 9 | ], 10 | "parts": { 11 | "final": [ 12 | "bo", 13 | "do", 14 | "doc", 15 | "go", 16 | "grin", 17 | "m" 18 | ], 19 | "mid": [ 20 | "a", 21 | "e", 22 | "i", 23 | "ia", 24 | "o", 25 | "oi", 26 | "u" 27 | ], 28 | "first": [ 29 | "B", 30 | "Dr", 31 | "Fr", 32 | "Mer", 33 | "Per", 34 | "S" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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 | } 82 | -------------------------------------------------------------------------------- /data/wisdom.txt: -------------------------------------------------------------------------------- 1 | The best way to a man's heart is to saw his breast plate open. ~Women's restroom, Murphy's, Champaign, Ill. 2 | Beauty is only a light switch away. ~Perkins Library, Duke University, Durham, N.C. 3 | I've decided that to raise my grades, I must lower my standards. ~Houghton Library, Harvard University, Cambridge, Mass. 4 | If Bush were captain of the Titanic, he'd say we were stopping for ice. ~Smoky Joe's, Philadelphia, Penna. 5 | Remember, it's not "How high are you?" it's "Hi, how are you?". ~Rest stop off Route 81, W. Va. 6 | God made pot. Man made beer. Who do you trust? ~The Irish Times, Washington, D.C 7 | Fighting for peace is like screwing for virginity. ~The Bayou, Baton Rouge, La. 8 | No matter how good she looks, some other guy is sick and tired of putting up with her shit. ~Men's restroom, Linda's Bar and Grill, Chapel Hill, N.C. 9 | To do is to be. (Descartes) To be is to do. (Voltaire) Do be do be do. (Frank Sinatra) ~Men's restroom, Greasewood Flats, Scottsdale, Ariz. 10 | At the feast of ego, everyone leaves hungry. ~Bentley's House of Coffee and Tea, Tucson, Ariz. 11 | It's hard to make a comeback when you haven't been anywhere. ~Written in the dust on the back of a bus, Wickenburg, Ariz. 12 | Make love, not war. Hell, do both - get married! ~Women's restroom, The Filling Station, Bozeman, Mont. 13 | If voting could really change things, it would be illegal. ~Revolution Books, New York, N.Y. 14 | A woman's rule of thumb: If it has tires or testicles, you're going to have trouble with it. ~Women's restroom, Dick's Last Resort, Dallas, Tex. 15 | JESUS SAVES! But wouldn't it be better if he had invested? ~Men's restroom, American University, Washington, D.C. 16 | If pro is the opposite of con, then what is the opposite of progress? Congress! ~Men's restroom, House of Representatives, Washington, D.C. 17 | Express Lane: Five beers or less. Sign over one of the urinals. ~Ed Debevic's, Phoenix, Ariz. 18 | You're too good for him. ~Sign over mirror in women's restroom, Ed Debevic's, Beverly Hills,Calif. 19 | No wonder you always go home alone. Sign over mirror in men's restroom. ~Ed Debevic's, Beverly Hills, California 20 | What are you looking up on the wall for? The joke is in your hands. ~Men's restroom, Lynagh's, Lexington, Ky. 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # CloudBot Docs 2 | 3 | Welcome to CloudBot! This set of documentation is split into three folders. 4 | 5 | ### Folder Structure 6 | 7 | - User: The users folder holds all the documentation for setting up a new CloudBot client instance, as well as the required configurations and needed APIs/external softwares. 8 | - Dev: The devs folder contains documention for developers who wish to get a reference of the CloudBot API and code paradigms. 9 | - Etc: This folder holds all the stuff that didn't relate to any of the other two at all. 10 | 11 | For each set of docs, a PDF and HTML version is also available. 12 | 13 | ## Refresh 14 | 15 | Refresh (this repo) is the newest CloudBot, which cleans up the internals of the bot and brings support for Python 3.4. 16 | 17 | ## Developers 18 | * [Luke Rogers](http://git.io/theluke) 19 | * [neersighted](http://git.io/neersighted) 20 | * [daboross](http://git.io/dabo) 21 | * [foxlet](http://git.io/foxlet) 22 | 23 | ### Thanks to 24 | * [rmmh](http://git.io/rmmh) 25 | * [The Noodle](https://github.com/thenoodle68) 26 | -------------------------------------------------------------------------------- /docs/dev/main.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/dev/main.md -------------------------------------------------------------------------------- /docs/user/configuration.md: -------------------------------------------------------------------------------- 1 | ###Configuration 2 | 3 | This left blank for now. 4 | -------------------------------------------------------------------------------- /docs/user/googlecustomsearch_id.md: -------------------------------------------------------------------------------- 1 | # Setting up the Google Custom Search Engine 2 | 3 | ## Introduction 4 | In this guide, we will cover the setup of the Custom Search Engine for use within CloudBot. This feature is used by: 5 | - plugins/google.py 6 | -------------------------------------------------------------------------------- /docs/user/googledevconsole_api.md: -------------------------------------------------------------------------------- 1 | # Setting up the Google Developers Console API 2 | 3 | ## Introduction 4 | In this guide, we will cover the setup of the GDC API for use within CloudBot. This API is used by: 5 | - plugins/books.py 6 | - plugins/youtube.py 7 | - plugins/google_translate.py 8 | 9 | ## 1 - Sign Up for the Google Developers Console 10 | You can create a GDC account at https://console.developers.google.com/. You need to create a Google account or use an existing one. 11 | 12 | ## 2 - Create a new project 13 | Select ***Create New Project*** if you haven't already done so: 14 | 15 | ![GDC Create a New Project](img/gdev_1.png?raw=true "Create a New Project") 16 | 17 | Give your bot a name (and optionally change the name of the project ID), agree to the Terms of Service, then select ***Create***. 18 | 19 | ![GDC Name Your Bot](img/gdev_2.png?raw=true "Name Your Bot") 20 | 21 | ## 3 - Enable APIs 22 | 23 | Once you have created the project, select it in the main panel (if it already hasn't been), then on the sidebar go to **APIs and Auth -> APIs**. Scroll through the list to select ***ON*** for the following services: 24 | 25 | - Books API 26 | - Youtube Data API v3 27 | - Geocoding API 28 | - Time Zone API 29 | - Optional: Google Custom Search API, if using Google Search 30 | - Optional: Translate API, if using Google Translate 31 | 32 | Optional APIs are only used if you got the right modules or a payment type to use. 33 | 34 | ![GDC Select the APIs](img/gdev_3.png?raw=true "Select the APIs") 35 | 36 | For each API, you may have to first accept their individual Terms of Service, then select ***Accept***. 37 | 38 | ![GDC Agree to the ToS](img/gdev_4.png?raw=true "Agree to the ToS") 39 | 40 | ## 4 - Generate an API Key 41 | GDC API services only need one key for all Google Services used. You must generate a key for each bot instance you plan to use. Go to **APIs and Auth -> Credentials** then select ***Create a new Key*** 42 | 43 | ![GDC Create a Key](img/gdev_5.png?raw=true "Create a Key") 44 | 45 | Select to create a ***Server Key*** 46 | 47 | ![GDC Server Key](img/gdev_6.png?raw=true "Server Key") 48 | 49 | Enter the Public IPs of the Cloudbot instance you plan to assign to this key. If you don't know it, running `wget -qO- http://icanhazip.com/` within your terminal should return it. Click ***Create***. 50 | 51 | ![GDC Enter IPs](img/gdev_7.png?raw=true "Enter IPs") 52 | 53 | Your new key should now appear on the main panel, simply copy it to the *google_dev_key* object in your CloudBot's configuration. 54 | 55 | ![GDC Copy the key](img/gdev_8.png?raw=true "Copy the key") 56 | -------------------------------------------------------------------------------- /docs/user/img/cse_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/cse_1.png -------------------------------------------------------------------------------- /docs/user/img/cse_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/cse_2.png -------------------------------------------------------------------------------- /docs/user/img/cse_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/cse_3.png -------------------------------------------------------------------------------- /docs/user/img/gdev_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_1.png -------------------------------------------------------------------------------- /docs/user/img/gdev_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_2.png -------------------------------------------------------------------------------- /docs/user/img/gdev_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_3.png -------------------------------------------------------------------------------- /docs/user/img/gdev_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_4.png -------------------------------------------------------------------------------- /docs/user/img/gdev_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_5.png -------------------------------------------------------------------------------- /docs/user/img/gdev_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_6.png -------------------------------------------------------------------------------- /docs/user/img/gdev_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_7.png -------------------------------------------------------------------------------- /docs/user/img/gdev_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/gdev_8.png -------------------------------------------------------------------------------- /docs/user/img/oc_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/oc_1.png -------------------------------------------------------------------------------- /docs/user/img/oc_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/oc_2.png -------------------------------------------------------------------------------- /docs/user/img/oc_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/oc_3.png -------------------------------------------------------------------------------- /docs/user/img/wn_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/docs/user/img/wn_1.png -------------------------------------------------------------------------------- /docs/user/octopart_api.md: -------------------------------------------------------------------------------- 1 | # Setting up the Octopart API 2 | 3 | ## Introduction 4 | In this guide, we will cover the setup of the Octopart API for use within CloudBot. This API is used by: 5 | - plugins/octopart.py 6 | 7 | ## 1 - Sign Up on the Octopart Website 8 | You must first create an Octopart API account over at https://octopart.com/api/home/. 9 | 10 | ![Sign Up for an Account](img/oc_1.png?raw=true "Sign Up for an Account") 11 | 12 | ## 2 - Create a new project in the Dashboard 13 | Once you got an account, you can proceed to make a new API application project [in the Dashboard](https://octopart.com/api/dashboard). 14 | 15 | ![Start a new Project](img/oc_2.png?raw=true "Start a new Project") 16 | 17 | ## 2 - Fill in the needed API information 18 | Next, enter a new application name for your CloudBot instance, as well as a webpage for your bot (or your organization), agree to the Terms of Use, then select "Register application". 19 | 20 | ![Fill the Form](img/oc_3.png?raw=true "Fill the Form") 21 | 22 | ## 3 - Get your API key 23 | Once in the dashboard, you can get the API key from the table shown. 24 | -------------------------------------------------------------------------------- /docs/user/optout.md: -------------------------------------------------------------------------------- 1 | # Hook OptOuts 2 | 3 | - The `core.optout` plugin allows channels to enable/disable specific hooks matching a pattern. 4 | - Both the channel and hook parameters accept glob patterns. 5 | - OptOut checks are done in order from the most specific patterns to the most broad. 6 | 7 | ### Disabling a hook 8 | `optout plugin.command_func disable` 9 | 10 | ### Enabling a globally disabled hook 11 | `optout plugin.command_func enable` 12 | 13 | ### Globally disabling a hook 14 | `optout #* plugin.command_func disable` 15 | 16 | ## Examples 17 | #### Disabling all attack commands in a channel 18 | `optout attacks.* disable` 19 | 20 | #### Allowing `attacks.compliment` while still not allowing other attacks 21 | `optout attacks.* disable` 22 | 23 | `optout attacks.compliment enable` 24 | 25 | #### Globally disable the quote command 26 | `optout #* quote.quote disable` 27 | -------------------------------------------------------------------------------- /docs/user/wordnik_api.md: -------------------------------------------------------------------------------- 1 | # Setting up the Wordnik API 2 | 3 | ## Introduction 4 | In this guide, we will cover the setup of the Wordnik API for use within CloudBot. This API is used by: 5 | - plugins/wordnik.py 6 | 7 | ## 1 - Sign Up for a Wordnik Developer Account 8 | You can create an account at http://developer.wordnik.com/. Once you have one, simply fill out the form at that very page. 9 | 10 | ![Sign Up for an Account](img/wn_1.png?raw=true "Sign Up for an Account") 11 | -------------------------------------------------------------------------------- /format_json.py: -------------------------------------------------------------------------------- 1 | """ 2 | Format all JSON files in the bot core in a consistent manor 3 | """ 4 | 5 | import collections 6 | import json 7 | from pathlib import Path 8 | 9 | path = Path().resolve() 10 | 11 | for file in path.rglob("*.json"): 12 | print(file) 13 | with file.open(encoding='utf8') as f: 14 | data = json.load(f, object_pairs_hook=collections.OrderedDict) 15 | 16 | with file.open('w', encoding='utf8') as f: 17 | print(json.dumps(data, indent=4), file=f) 18 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/plugins/__init__.py -------------------------------------------------------------------------------- /plugins/animal_gifs.py: -------------------------------------------------------------------------------- 1 | """ 2 | All GIFs courtesy of http://bestanimations.com/ 3 | """ 4 | import random 5 | from urllib.parse import urljoin 6 | 7 | from cloudbot import hook 8 | from cloudbot.util.http import get_soup 9 | 10 | BASE_URL = "http://bestanimations.com/Animals/Mammals/Dogs/" 11 | DOG_PAGES = ( 12 | "Dogs.html", 13 | "Dogs2.html", # Pugs 14 | "Dogs3.html", # Puppies 15 | ) 16 | 17 | 18 | def get_gifs(url): 19 | soup = get_soup(url) 20 | container = soup.find('div', class_="row") 21 | gifs = [urljoin(url, elem["src"]) for elem in container.find_all('img')] 22 | return gifs 23 | 24 | 25 | def get_random_gif(url): 26 | return random.choice(get_gifs(url)) 27 | 28 | 29 | @hook.command(autohelp=False) 30 | def doggifs(reply): 31 | """- Returns a random dog GIF from http://bestanimations.com/""" 32 | page = random.choice(DOG_PAGES) 33 | url = urljoin(BASE_URL, page) 34 | try: 35 | return get_random_gif(url) 36 | except Exception: 37 | reply("Error occurred when retrieving GIF") 38 | raise 39 | -------------------------------------------------------------------------------- /plugins/autojoin.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from collections import defaultdict 3 | from threading import RLock 4 | 5 | from sqlalchemy import PrimaryKeyConstraint, Column, String, Table, and_ 6 | from sqlalchemy.exc import IntegrityError 7 | 8 | from cloudbot import hook 9 | from cloudbot.util import database 10 | 11 | table = Table( 12 | 'autojoin', 13 | database.metadata, 14 | Column('conn', String), 15 | Column('chan', String), 16 | PrimaryKeyConstraint('conn', 'chan') 17 | ) 18 | 19 | chan_cache = defaultdict(set) 20 | db_lock = RLock() 21 | 22 | 23 | def get_channels(db, conn): 24 | return db.execute(table.select().where(table.c.conn == conn.name.casefold())).fetchall() 25 | 26 | 27 | @hook.on_start 28 | def load_cache(db): 29 | with db_lock: 30 | chan_cache.clear() 31 | for row in db.execute(table.select()): 32 | chan_cache[row['conn']].add(row['chan']) 33 | 34 | 35 | @hook.irc_raw('376') 36 | @asyncio.coroutine 37 | def do_joins(conn): 38 | join_throttle = conn.config.get("join_throttle", 0.4) 39 | for chan in chan_cache[conn.name]: 40 | conn.join(chan) 41 | yield from asyncio.sleep(join_throttle) 42 | 43 | 44 | @hook.irc_raw('JOIN', singlethread=True) 45 | def add_chan(db, conn, chan, nick): 46 | chans = chan_cache[conn.name] 47 | chan = chan.casefold() 48 | if nick.casefold() == conn.nick.casefold() and chan not in chans: 49 | with db_lock: 50 | try: 51 | db.execute(table.insert().values(conn=conn.name.casefold(), chan=chan.casefold())) 52 | except IntegrityError: 53 | db.rollback() 54 | else: 55 | db.commit() 56 | 57 | load_cache(db) 58 | 59 | 60 | @hook.irc_raw('PART', singlethread=True) 61 | def on_part(db, conn, chan, nick): 62 | if nick.casefold() == conn.nick.casefold(): 63 | with db_lock: 64 | db.execute( 65 | table.delete().where(and_(table.c.conn == conn.name.casefold(), table.c.chan == chan.casefold()))) 66 | db.commit() 67 | 68 | load_cache(db) 69 | 70 | 71 | @hook.irc_raw('KICK', singlethread=True) 72 | def on_kick(db, conn, chan, target): 73 | on_part(db, conn, chan, target) 74 | -------------------------------------------------------------------------------- /plugins/bible.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from cloudbot import hook 4 | 5 | 6 | @hook.command("bible", "passage", singlethread=True) 7 | def bible(text, reply): 8 | """ - Prints the specified passage from the Bible""" 9 | passage = text.strip() 10 | params = { 11 | 'passage': passage, 12 | 'formatting': 'plain', 13 | 'type': 'json' 14 | } 15 | try: 16 | r = requests.get("https://labs.bible.org/api", params=params) 17 | r.raise_for_status() 18 | response = r.json()[0] 19 | except Exception: 20 | reply("Something went wrong, either you entered an invalid passage or the API is down.") 21 | raise 22 | 23 | book = response['bookname'] 24 | ch = response['chapter'] 25 | ver = response['verse'] 26 | txt = response['text'] 27 | return "\x02{} {}:{}\x02 {}".format(book, ch, ver, txt) 28 | -------------------------------------------------------------------------------- /plugins/books.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests import HTTPError 3 | 4 | from cloudbot import hook 5 | from cloudbot.util import formatting, web 6 | 7 | base_url = 'https://www.googleapis.com/books/v1/' 8 | book_search_api = base_url + 'volumes?' 9 | 10 | 11 | @hook.on_start() 12 | def load_key(bot): 13 | global dev_key 14 | dev_key = bot.config.get("api_keys", {}).get("google_dev_key", None) 15 | 16 | 17 | @hook.command("books", "gbooks") 18 | def books(text, reply): 19 | """ - Searches Google Books for .""" 20 | if not dev_key: 21 | return "This command requires a Google Developers Console API key." 22 | 23 | request = requests.get(book_search_api, params={"q": text, "key": dev_key, "country": "US"}) 24 | 25 | try: 26 | request.raise_for_status() 27 | except HTTPError: 28 | reply("Bing API error occurred.") 29 | raise 30 | 31 | json = request.json() 32 | 33 | if json.get('error'): 34 | if json['error']['code'] == 403: 35 | print(json['error']['message']) 36 | return "The Books API is off in the Google Developers Console (or check the console)." 37 | else: 38 | return 'Error performing search.' 39 | 40 | if json['totalItems'] == 0: 41 | return 'No results found.' 42 | 43 | book = json['items'][0]['volumeInfo'] 44 | title = book['title'] 45 | try: 46 | author = book['authors'][0] 47 | except KeyError: 48 | try: 49 | author = book['publisher'] 50 | except KeyError: 51 | author = "Unknown Author" 52 | 53 | try: 54 | description = formatting.truncate_str(book['description'], 130) 55 | except KeyError: 56 | description = "No description available." 57 | 58 | try: 59 | year = book['publishedDate'][:4] 60 | except KeyError: 61 | year = "No Year" 62 | 63 | try: 64 | page_count = book['pageCount'] 65 | pages = ' - \x02{:,}\x02 page{}'.format(page_count, "s"[page_count == 1:]) 66 | except KeyError: 67 | pages = '' 68 | 69 | link = web.shorten(book['infoLink'], service="goo.gl", key=dev_key) 70 | 71 | return "\x02{}\x02 by \x02{}\x02 ({}){} - {} - {}".format(title, author, year, pages, description, link) 72 | -------------------------------------------------------------------------------- /plugins/brew.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests import HTTPError 3 | 4 | from cloudbot import hook 5 | 6 | api_url = "http://api.brewerydb.com/v2/search?format=json" 7 | 8 | 9 | @hook.on_start() 10 | def load_key(bot): 11 | global api_key 12 | api_key = bot.config.get("api_keys", {}).get("brewerydb", None) 13 | 14 | 15 | @hook.command('brew') 16 | def brew(text, reply): 17 | """ - returns the first brewerydb search result for """ 18 | 19 | if not api_key: 20 | return "No brewerydb API key set." 21 | 22 | params = {'key': api_key, 'type': 'beer', 'withBreweries': 'Y', 'q': text} 23 | request = requests.get(api_url, params=params) 24 | 25 | try: 26 | request.raise_for_status() 27 | except HTTPError: 28 | reply("Failed to fetch info ({})".format(request.status_code)) 29 | raise 30 | 31 | response = request.json() 32 | 33 | output = "No results found." 34 | 35 | try: 36 | if 'totalResults' in response: 37 | beer = response['data'][0] 38 | brewery = beer['breweries'][0] 39 | 40 | style = 'unknown style' 41 | if 'style' in beer: 42 | style = beer['style']['shortName'] 43 | 44 | abv = '?.?' 45 | if 'abv' in beer: 46 | abv = beer['abv'] 47 | 48 | url = '[no website found]' 49 | if 'website' in brewery: 50 | url = brewery['website'] 51 | 52 | content = { 53 | 'name': beer['nameDisplay'], 54 | 'style': style, 55 | 'abv': abv, 56 | 'brewer': brewery['name'], 57 | 'url': url 58 | } 59 | 60 | output = "{name} by {brewer} ({style}, {abv}% ABV) - {url}" \ 61 | .format(**content) 62 | 63 | except Exception as e: 64 | print(e) 65 | reply("Error parsing results.") 66 | raise 67 | 68 | return output 69 | -------------------------------------------------------------------------------- /plugins/cats.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests import HTTPError 3 | 4 | from cloudbot import hook 5 | 6 | 7 | def get_data(url, reply, bot, params=None): 8 | try: 9 | r = requests.get(url, headers={'User-Agent': bot.user_agent}, params=params) 10 | r.raise_for_status() 11 | except HTTPError: 12 | reply("API error occurred.") 13 | raise 14 | 15 | return r 16 | 17 | 18 | @hook.command(autohelp=False) 19 | def cats(reply, bot): 20 | """- gets a fucking fact about cats.""" 21 | r = get_data('https://catfact.ninja/fact', reply, bot, params={'max_length': 100}) 22 | json = r.json() 23 | response = json['fact'] 24 | return response 25 | 26 | 27 | @hook.command(autohelp=False) 28 | def catgifs(reply, bot): 29 | """- gets a fucking cat gif.""" 30 | r = get_data("http://marume.herokuapp.com/random.gif", reply, bot) 31 | return "OMG A CAT GIF: {}".format(r.url) 32 | -------------------------------------------------------------------------------- /plugins/chatbot.py: -------------------------------------------------------------------------------- 1 | from cleverwrap import CleverWrap 2 | 3 | from cloudbot import hook 4 | 5 | 6 | @hook.on_start() 7 | def get_key(bot): 8 | global api_key, cb 9 | api_key = bot.config.get("api_keys", {}).get("cleverbot", None) 10 | cb = CleverWrap(api_key) 11 | 12 | 13 | @hook.command("ask", "gonzo", "gonzobot", "cleverbot", "cb") 14 | def chitchat(text): 15 | """ - chat with cleverbot.com""" 16 | if not api_key: 17 | return "Please add an API key from http://www.cleverbot.com/api to enable this feature." 18 | return cb.say(text) 19 | -------------------------------------------------------------------------------- /plugins/cheer.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | from pathlib import Path 4 | 5 | from cloudbot import hook 6 | 7 | cheer_re = re.compile(r'\\o/', re.IGNORECASE) 8 | 9 | cheers = [] 10 | 11 | 12 | @hook.on_start 13 | def load_cheers(bot): 14 | cheers.clear() 15 | data_file = Path(bot.data_dir) / "cheers.txt" 16 | with data_file.open(encoding='utf-8') as f: 17 | cheers.extend(line.strip() for line in f if not line.startswith('//')) 18 | 19 | 20 | @hook.regex(cheer_re) 21 | def cheer(chan, message): 22 | """ 23 | :type chan: str 24 | """ 25 | shit = random.choice(cheers) 26 | message(shit, chan) 27 | -------------------------------------------------------------------------------- /plugins/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardslabs/CloudBot/a0853d29e01e755fa9ce92e5f7394704840e9efb/plugins/core/__init__.py -------------------------------------------------------------------------------- /plugins/core/chan_log.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from requests.exceptions import RequestException 4 | 5 | from cloudbot import hook 6 | from cloudbot.util import web 7 | 8 | 9 | def _dump_attrs(obj): 10 | for name in dir(obj): 11 | if not name.startswith('_'): 12 | yield name, getattr(obj, name, None) 13 | 14 | 15 | @hook.post_hook 16 | def on_hook_end(error, launched_hook, launched_event, admin_log): 17 | should_broadcast = True 18 | if error is not None: 19 | messages = [ 20 | "Error occurred in {}.{}".format(launched_hook.plugin.title, launched_hook.function_name) 21 | ] 22 | 23 | try: 24 | lines = traceback.format_exception(*error) 25 | last_line = lines[-1] 26 | messages.append(last_line.strip()) 27 | except Exception as e: 28 | messages.append("Error occurred while formatting error {}: {}".format(type(e), e)) 29 | else: 30 | try: 31 | url = web.paste('\n'.join(lines)) 32 | messages.append("Traceback: " + url) 33 | except Exception as e: 34 | messages.append("Error occurred while gathering traceback {}: {}".format(type(e), e)) 35 | 36 | try: 37 | lines = ["{} = {}".format(k, v) for k, v in _dump_attrs(launched_event)] 38 | exc_type, exc, exc_tb = error 39 | 40 | lines.append("") 41 | lines.append("Error data:") 42 | lines.extend("{} = {}".format(k, v) for k, v in _dump_attrs(exc)) 43 | 44 | if isinstance(exc, RequestException): 45 | if exc.request is not None: 46 | req = exc.request 47 | lines.append("") 48 | lines.append("Request Info:") 49 | lines.extend("{} = {}".format(k, v) for k, v in _dump_attrs(req)) 50 | 51 | if exc.response is not None: 52 | response = exc.response 53 | lines.append("") 54 | lines.append("Response Info:") 55 | lines.extend("{} = {}".format(k, v) for k, v in _dump_attrs(response)) 56 | 57 | url = web.paste('\n'.join(lines)) 58 | messages.append("Event: " + url) 59 | except Exception as e: 60 | messages.append("Error occurred while gathering error data {}: {}".format(type(e), e)) 61 | 62 | for message in messages: 63 | admin_log(message, should_broadcast) 64 | -------------------------------------------------------------------------------- /plugins/core/core_connect.py: -------------------------------------------------------------------------------- 1 | from cloudbot import hook 2 | 3 | 4 | @hook.connect(priority=0, clients="irc") 5 | def conn_pass(conn): 6 | conn.set_pass(conn.config["connection"].get("password")) 7 | 8 | 9 | @hook.connect(priority=10) 10 | def conn_nick(conn): 11 | conn.set_nick(conn.nick) 12 | 13 | 14 | @hook.connect(priority=20, clients="irc") 15 | def conn_user(conn): 16 | conn.cmd( 17 | "USER", conn.config.get('user', 'cloudbot'), "3", "*", 18 | conn.config.get('realname', 'CloudBot - https://git.io/CloudBot') 19 | ) 20 | -------------------------------------------------------------------------------- /plugins/core/core_ctcp.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import cloudbot 5 | from cloudbot import hook 6 | from cloudbot.event import EventType 7 | 8 | 9 | # CTCP responses 10 | @asyncio.coroutine 11 | @hook.event([EventType.other]) 12 | def ctcp_version(notice, irc_ctcp_text): 13 | if irc_ctcp_text: 14 | if irc_ctcp_text.startswith("VERSION"): 15 | notice("\x01VERSION gonzobot a fork of Cloudbot {} - https://snoonet.org/gonzobot\x01".format( 16 | cloudbot.__version__)) 17 | elif irc_ctcp_text.startswith("PING"): 18 | notice('\x01{}\x01'.format( 19 | irc_ctcp_text)) # Bot should return exactly what the user sends as the ping parameter 20 | elif irc_ctcp_text.startswith("TIME"): 21 | notice('\x01TIME {}\x01'.format(time.asctime())) # General convention is to return the asc time 22 | -------------------------------------------------------------------------------- /plugins/core/core_hooks.py: -------------------------------------------------------------------------------- 1 | from cloudbot import hook 2 | from cloudbot.hook import Priority 3 | 4 | 5 | @hook.sieve(priority=Priority.LOWEST) 6 | def cmd_autohelp(bot, event, _hook): 7 | if _hook.type == "command" and _hook.auto_help and not event.text and _hook.doc is not None: 8 | event.notice_doc() 9 | return None 10 | 11 | return event 12 | 13 | 14 | @hook.post_hook(priority=Priority.LOWEST) 15 | def do_reply(result, error, launched_event, launched_hook): 16 | if launched_hook.type in ("sieve", "on_start", "on_stop"): 17 | return 18 | 19 | if error is None and result is not None: 20 | if isinstance(result, (list, tuple)): 21 | # if there are multiple items in the response, return them on multiple lines 22 | launched_event.reply(*result) 23 | else: 24 | launched_event.reply(result) 25 | -------------------------------------------------------------------------------- /plugins/core/core_out.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core filters for IRC raw lines 3 | """ 4 | 5 | from cloudbot import hook 6 | from cloudbot.hook import Priority 7 | from cloudbot.util import colors 8 | 9 | NEW_LINE_TRANS_TBL = str.maketrans({ 10 | '\r': None, 11 | '\n': None, 12 | '\0': None, 13 | }) 14 | 15 | 16 | @hook.irc_out(priority=Priority.HIGHEST) 17 | def strip_newlines(line, conn): 18 | """ 19 | Removes newline characters from a message 20 | :param line: str 21 | :param conn: cloudbot.clients.irc.IrcClient 22 | :return: str 23 | """ 24 | do_strip = conn.config.get("strip_newlines", True) 25 | if do_strip: 26 | return line.translate(NEW_LINE_TRANS_TBL) 27 | else: 28 | return line 29 | 30 | 31 | @hook.irc_out(priority=Priority.HIGH) 32 | def truncate_line(line, conn): 33 | line_len = conn.config.get("max_line_length", 510) 34 | return line[:line_len] + "\r\n" 35 | 36 | 37 | @hook.irc_out(priority=Priority.LOWEST) 38 | def encode_line(line, conn): 39 | if not isinstance(line, str): 40 | return line 41 | 42 | encoding = conn.config.get("encoding", "utf-8") 43 | errors = conn.config.get("encoding_errors", "replace") 44 | return line.encode(encoding, errors) 45 | 46 | 47 | @hook.irc_out(priority=Priority.HIGH) 48 | def strip_command_chars(parsed_line, conn, line): 49 | chars = conn.config.get("strip_cmd_chars", "!.@;$") 50 | if chars and parsed_line and parsed_line.command == "PRIVMSG" and parsed_line.parameters[-1][0] in chars: 51 | new_msg = colors.parse("$(red)[!!]$(clear) ") + parsed_line.parameters[-1] 52 | parsed_line.parameters[-1] = new_msg 53 | parsed_line.has_trail = True 54 | return parsed_line 55 | 56 | return line 57 | -------------------------------------------------------------------------------- /plugins/core/plugin_control.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from operator import itemgetter 3 | from pathlib import Path 4 | 5 | from cloudbot import hook 6 | from cloudbot.util import web 7 | from cloudbot.util.formatting import gen_markdown_table 8 | 9 | 10 | @hook.command(permissions=["botcontrol"], autohelp=False) 11 | def pluginlist(bot): 12 | """- List all currently loaded plugins""" 13 | manager = bot.plugin_manager 14 | plugins = [ 15 | (plugin.title, str(Path(plugin.file_path).resolve().relative_to(bot.base_dir))) 16 | for plugin in manager.plugins.values() 17 | ] 18 | plugins.sort(key=itemgetter(0)) 19 | table = gen_markdown_table(["Plugin", "Path"], plugins) 20 | return web.paste(table, service="hastebin") 21 | 22 | 23 | @hook.command(permissions=["botcontrol"]) 24 | @asyncio.coroutine 25 | def pluginload(bot, text, reply): 26 | """ - (Re)load manually""" 27 | manager = bot.plugin_manager 28 | path = str(Path(text.strip()).resolve()) 29 | was_loaded = path in manager.plugins 30 | coro = bot.plugin_manager.load_plugin(path) 31 | 32 | try: 33 | yield from coro 34 | except Exception: 35 | reply("Plugin failed to load.") 36 | raise 37 | else: 38 | return "Plugin {}loaded successfully.".format("re" if was_loaded else "") 39 | 40 | 41 | @hook.command(permissions=["botcontrol"]) 42 | @asyncio.coroutine 43 | def pluginunload(bot, text): 44 | """ - Unload manually""" 45 | manager = bot.plugin_manager 46 | path = str(Path(text.strip()).resolve()) 47 | is_loaded = path in manager.plugins 48 | 49 | if not is_loaded: 50 | return "Plugin not loaded, unable to unload." 51 | 52 | if (yield from manager.unload_plugin(path)): 53 | return "Plugin unloaded successfully." 54 | 55 | return "Plugin failed to unload." 56 | -------------------------------------------------------------------------------- /plugins/correction.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from cloudbot import hook 4 | from cloudbot.util.formatting import ireplace 5 | 6 | correction_re = re.compile(r"^[sS]/(?:(.*?)(? {}" 40 | 41 | mod_msg = ireplace(re.escape(mod_msg), find_esc, "\x02" + replace_esc + "\x02") 42 | 43 | mod_msg = unescape_re.sub(r"\1", mod_msg) 44 | 45 | message("Correction, {}".format(fmt.format(name, mod_msg))) 46 | 47 | if nick.lower() == name.lower(): 48 | msg = ireplace(re.escape(msg), find_esc, replace_esc) 49 | msg = unescape_re.sub(r"\1", msg) 50 | conn.history[chan].append((name, timestamp, msg)) 51 | 52 | break 53 | -------------------------------------------------------------------------------- /plugins/cypher.py: -------------------------------------------------------------------------------- 1 | """ 2 | cypher.py 3 | 4 | Ciphers and deciphers strings. 5 | 6 | Created By: 7 | - Tom 8 | 9 | Modified By: 10 | - Fletcher Boyd 11 | - Dabo Ross 12 | - Luke Rogers 13 | 14 | License: 15 | GPL v3 16 | """ 17 | 18 | import base64 19 | import binascii 20 | 21 | from cloudbot import hook 22 | 23 | 24 | def encode(password, text): 25 | """ 26 | :type password: str 27 | :type text: str 28 | """ 29 | enc = [] 30 | for i in range(len(text)): 31 | key_c = password[i % len(password)] 32 | enc_c = chr((ord(text[i]) + ord(key_c)) % 256) 33 | enc.append(enc_c) 34 | return base64.urlsafe_b64encode("".join(enc).encode()).decode() 35 | 36 | 37 | def decode(password, encoded, notice): 38 | """ 39 | :type password: str 40 | :type encoded: str 41 | """ 42 | dec = [] 43 | try: 44 | encoded_bytes = base64.urlsafe_b64decode(encoded.encode()).decode() 45 | except binascii.Error: 46 | notice("Invalid input '{}'".format(encoded)) 47 | return 48 | for i in range(len(encoded_bytes)): 49 | key_c = password[i % len(password)] 50 | dec_c = chr((256 + ord(encoded_bytes[i]) - ord(key_c)) % 256) 51 | dec.append(dec_c) 52 | return "".join(dec) 53 | 54 | 55 | @hook.command("cypher", "cipher") 56 | def cypher(text, message, notice): 57 | """ -- cyphers with """ 58 | split = text.split(None, 1) 59 | if len(split) < 2: 60 | notice(cypher.__doc__) 61 | return 62 | password = split[0] 63 | plaintext = split[1] 64 | message(" " + encode(password, plaintext)) 65 | 66 | 67 | @hook.command("decypher", "decipher") 68 | def decypher(text, message, notice): 69 | """ - decyphers with """ 70 | split = text.split(None, 1) 71 | if len(split) < 2: 72 | notice(decypher.__doc__) 73 | return 74 | password = split[0] 75 | encoded = split[1] 76 | message(" " + decode(password, encoded, notice)) 77 | -------------------------------------------------------------------------------- /plugins/deals.py: -------------------------------------------------------------------------------- 1 | import feedparser 2 | 3 | from cloudbot import hook 4 | from cloudbot.util import web 5 | 6 | 7 | @hook.command('meh', autohelp=False) 8 | def meh(): 9 | """- List the current meh.com deal.""" 10 | url = "https://meh.com/deals.rss" 11 | 12 | feed = feedparser.parse(url) 13 | title = feed.entries[0].title 14 | link = web.try_shorten(feed.entries[0].link) 15 | 16 | return "meh.com: {} ({})".format(title, link) 17 | 18 | 19 | @hook.command('slickdeals', autohelp=False) 20 | def slickdeals(): 21 | """- List the top 3 frontpage slickdeals.net deals.""" 22 | url = "https://slickdeals.net/newsearch.php?mode=frontpage&searcharea=deals&searchin=first&rss=1" 23 | 24 | feed = feedparser.parse(url) 25 | items = ( 26 | "{} ({})".format(item.title, web.try_shorten(item.link)) 27 | for item in feed.entries[:3] 28 | ) 29 | 30 | out = "slickdeals.net: " + ' \u2022 '.join(items) 31 | 32 | return out 33 | -------------------------------------------------------------------------------- /plugins/dig.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from cloudbot import hook 4 | 5 | 6 | @hook.command 7 | def dig(text, nick, notice): 8 | """ - returns a list of records for the specified domain valid record types are A, NS, TXT, and MX. If a record type is not chosen A will be the default.""" 9 | args = text.split() 10 | domain = args.pop(0) 11 | 12 | if args: 13 | rtype = args.pop(0).upper() 14 | else: 15 | rtype = "A" 16 | 17 | if rtype not in ("A", "NS", "MX", "TXT"): 18 | rtype = "A" 19 | 20 | url = "http://dig.jsondns.org/IN/{}/{}".format(domain, rtype) 21 | r = requests.get(url) 22 | r.raise_for_status() 23 | results = r.json() 24 | if results['header']['rcode'] == "NXDOMAIN": 25 | return "no dns record for {} was found".format(domain) 26 | notice("The following records were found for \x02{}\x02: ".format(domain), nick) 27 | for r in range(len(results['answer'])): 28 | domain = results['answer'][r]['name'] 29 | rtype = results['answer'][r]['type'] 30 | ttl = results['answer'][r]['ttl'] 31 | if rtype == "MX": 32 | rdata = results['answer'][r]['rdata'][1] 33 | elif rtype == "TXT": 34 | rdata = results['answer'][r]['rdata'][0] 35 | else: 36 | rdata = results['answer'][r]['rdata'] 37 | notice("name: \x02{}\x02 type: \x02{}\x02 ttl: \x02{}\x02 rdata: \x02{}\x02".format( 38 | domain, rtype, ttl, rdata), nick) 39 | -------------------------------------------------------------------------------- /plugins/dogpile.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | from urllib import parse 4 | 5 | import requests 6 | from bs4 import BeautifulSoup 7 | 8 | from cloudbot import hook 9 | 10 | search_url = "http://dogpile.com/search" 11 | 12 | HEADERS = { 13 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19' 14 | } 15 | 16 | 17 | @hook.command("dpis", "gis") 18 | def dogpileimage(text): 19 | """ - Uses the dogpile search engine to search for images.""" 20 | image_url = search_url + "/images" 21 | params = {'q': " ".join(text.split())} 22 | r = requests.get(image_url, params=params, headers=HEADERS) 23 | r.raise_for_status() 24 | soup = BeautifulSoup(r.content) 25 | data = soup.find_all("script")[6].string 26 | link_re = re.compile('"url":"(.*?)",') 27 | linklist = link_re.findall(data) 28 | if not linklist: 29 | return "No results returned." 30 | 31 | image = parse.unquote(parse.unquote(random.choice(linklist)).split('ru=')[1].split('&')[0]) 32 | return image 33 | 34 | 35 | @hook.command("dp", "g", "dogpile") 36 | def dogpile(text): 37 | """ - Uses the dogpile search engine to find shit on the web.""" 38 | web_url = search_url + "/web" 39 | params = {'q': " ".join(text.split())} 40 | r = requests.get(web_url, params=params, headers=HEADERS) 41 | r.raise_for_status() 42 | soup = BeautifulSoup(r.content) 43 | results = soup.find('div', id="webResults") 44 | if not results: 45 | return "No results found." 46 | 47 | result_url = parse.unquote( 48 | parse.unquote(results.find_all('a', {'class': 'resultDisplayUrl'})[0]['href']).split( 49 | 'ru=')[1].split('&')[0]) 50 | result_description = results.find_all('div', {'class': 'resultDescription'})[0].text 51 | return "{} -- \x02{}\x02".format(result_url, result_description) 52 | -------------------------------------------------------------------------------- /plugins/domainr.py: -------------------------------------------------------------------------------- 1 | from cloudbot import hook 2 | from cloudbot.util import http 3 | 4 | formats = { 5 | "taken": "\x034{domain}\x0f{path}", 6 | "available": "\x033{domain}\x0f{path}", 7 | "other": "\x031{domain}\x0f{path}" 8 | } 9 | 10 | 11 | def format_domain(domain): 12 | """ 13 | :type domain: dict[str, str] 14 | """ 15 | if domain["availability"] in formats: 16 | domainformat = formats[domain["availability"]] 17 | else: 18 | domainformat = formats["other"] 19 | return domainformat.format(**domain) 20 | 21 | 22 | @hook.command("domain", "domainr") 23 | def domainr(text): 24 | """ - uses domain.nr's API to search for a domain, and similar domains 25 | :type text: str 26 | """ 27 | try: 28 | data = http.get_json('http://domai.nr/api/json/search?q=' + text) 29 | except (http.URLError, http.HTTPError): 30 | return "Unable to get data for some reason. Try again later." 31 | if data['query'] == "": 32 | return "An error occurred: {status} - {message}".format(**data['error']) 33 | 34 | domains = [format_domain(domain) for domain in data["results"]] 35 | return "Domains: {}".format(", ".join(domains)) 36 | -------------------------------------------------------------------------------- /plugins/dragonvale.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import requests 4 | from bs4 import BeautifulSoup 5 | from requests import HTTPError 6 | 7 | from cloudbot import hook 8 | from cloudbot.util.timeparse import time_parse 9 | 10 | search_url = "http://dragonvale.wikia.com/api/v1/Search/list" 11 | 12 | egg_calc_url = "http://www.dragonvalebreedingguide.com/dragonvale-calculator" 13 | 14 | 15 | def striphtml(data): 16 | string = re.compile(r'<.*?>') 17 | return string.sub('', data) 18 | 19 | 20 | @hook.command("dragon", "ds") 21 | def dragonsearch(text, reply): 22 | """ - Searches the dragonvale wiki for the specified text.""" 23 | params = { 24 | "query": text.strip(), 25 | "limit": 1 26 | } 27 | 28 | r = requests.get(search_url, params=params) 29 | 30 | try: 31 | r.raise_for_status() 32 | except HTTPError: 33 | reply("The API returned error code {}.".format(r.status_code)) 34 | raise 35 | 36 | if not r.status_code == 200: 37 | return "The API returned error code {}.".format(r.status_code) 38 | 39 | data = r.json()["items"][0] 40 | out = "\x02{}\x02 -- {}: {}".format(data["title"], striphtml(data["snippet"]).split("…")[0].strip(), 41 | data["url"]) 42 | return out 43 | 44 | 45 | @hook.command("eggcalc", "dragoncalc", "dc") 46 | def egg_calculator(text): 47 | """