text:
# crowdcontrol.py by craisins in 2014
# Bot must have some sort of op or admin privileges to be useful

import re
import time
from util import hook

# Use "crowdcontrol" array in config
# syntax
# rule:
#   re: RegEx. regular expression to match
#   msg: String. message to display either with kick or as a warning
#   kick: Integer. 1 for True, 0 for False on if to kick user
#   ban_length: Integer. (optional) Length of time (seconds) to ban user. (-1 to never unban, 0 to not ban, > 1 for time)

@hook.regex(r".*")
def crowdcontrol(inp, kick=None, ban=None, unban=None, reply=None, bot=None):
    inp = inp.group(0)
    for rule in bot.config.get("crowdcontrol", []):
        if re.search(rule["re"], inp) is not None:
            should_kick = rule.get("kick", 0)
            ban_length = rule.get("ban_length", 0)
            reason = rule.get("msg")
            if ban_length != 0:
                ban()
            if should_kick:
                kick(reason=reason)
            elif "msg" in rule:
                reply(reason)
            if ban_length > 0:
                time.sleep(ban_length)
                unban()


text:
# -*- coding: utf-8 -*-
from __future__ import division, unicode_literals
from builtins import range, object
from past.utils import old_div
import math
import random
import re
import threading

from util import hook

def sanitize(s):
    return re.sub(r"[\x00-\x1f]", "", s)

@hook.command
def munge(inp, munge_count=0):
    reps = 0
    for n in range(len(inp)):
        rep = character_replacements.get(inp[n])
        if rep:
            inp = inp[:n] + rep + inp[n + 1 :]
            reps += 1
            if reps == munge_count:
                break
    return inp

class PaginatingWinnower(object):
    def __init__(self):
        self.lock = threading.Lock()
        self.inputs = {}

    def winnow(self, inputs, limit=400, ordered=False):
        "remove random elements from the list until it's short enough"
        with self.lock:
            combiner = lambda l: ", ".join(sorted(l))

            # try to remove elements that were *not* removed recently
            inputs_sorted = combiner(inputs)
            if inputs_sorted in self.inputs:
                same_input = True
                recent = self.inputs[inputs_sorted]
                if len(recent) == len(inputs):
                    recent.clear()
            else:
                same_input = False
                if len(self.inputs) >= 100:
                    # a true lru is effort, random replacement is easy
                    self.inputs.pop(random.choice(list(self.inputs)))
                recent = set()
                self.inputs[inputs_sorted] = recent

            suffix = ""

            while len(combiner(inputs)) >= limit:
                if same_input and any(inp in recent for inp in inputs):
                    if ordered:
                        for inp in recent:
                            if inp in inputs:
                                inputs.remove(inp)
                    else:
                        inputs.remove(
                            random.choice([inp for inp in inputs if inp in recent])
                        )
                else:
                    if ordered:
                        inputs.pop()
                    else:
                        inputs.pop(random.randint(0, len(inputs) - 1))
                suffix = " ..."

            recent.update(inputs)
            return combiner(inputs) + suffix

winnow = PaginatingWinnower().winnow

def add_tag(db, chan, nick, subject):
    match = db.execute(
        "select * from tag where lower(nick)=lower(?) and"
        " chan=? and lower(subject)=lower(?)",
        (nick, chan, subject),
    ).fetchall()
    if match:
        return "already tagged"

    db.execute(
        "replace into tag(chan, subject, nick) values(?,?,?)", (chan, subject, nick)
    )
    db.commit()

    return "tag added"

def delete_tag(db, chan, nick, del_tag):
    count = db.execute(
        "delete from tag where lower(nick)=lower(?) and"
        " chan=? and lower(subject)=lower(?)",
        (nick, chan, del_tag),
    ).rowcount
    db.commit()

    if count:
        return "deleted"
    else:
        return "tag not found"

def get_tag_counts_by_chan(db, chan):
    tags = db.execute(
        "select subject, count(*) from tag where chan=?"
        " group by lower(subject)"
        " order by lower(subject)",
        (chan,),
    ).fetchall()

    tags.sort(key=lambda x: x[1], reverse=True)
    if not tags:
        return "no tags in %s" % chan
    return winnow(["%s (%d)" % row for row in tags], ordered=True)

def get_tags_by_nick(db, chan, nick):
    return db.execute(
        "select subject from tag where lower(nick)=lower(?)"
        " and chan=?"
        " order by lower(subject)",
        (nick, chan),
    ).fetchall()

def get_tags_string_by_nick(db, chan, nick):
    tags = get_tags_by_nick(db, chan, nick)

    if tags:
        return 'tags for "%s": ' % munge(nick, 1) + winnow([tag[0] for tag in tags])
    else:
        return ""

def get_nicks_by_tagset(db, chan, tagset):
    nicks = None
    for tag in tagset.split("&"):
        tag = tag.strip()

        current_nicks = db.execute(
            "select lower(nick) from tag where " + "lower(subject)=lower(?)"
            " and chan=?",
            (tag, chan),
        ).fetchall()

        if not current_nicks:
            return "tag '%s' not found" % tag

        if nicks is None:
            nicks = set(current_nicks)
        else:
            nicks.intersection_update(current_nicks)

    nicks = [munge(x[0], 1) for x in sorted(nicks)]
    if not nicks:
        return 'no nicks found with tags "%s"' % tagset
    return 'nicks tagged "%s": ' % tagset + winnow(nicks)

@hook.command
def tag(inp, chan="", db=None):
    ".tag <nick> <tag> -- marks <nick> as <tag> {related: .untag, .tags, .tagged, .is}"

    db.execute("create table if not exists tag(chan, subject, nick)")

    add = re.match(r"(\S+) (.+)", inp)

    if add:
        nick, subject = add.groups()
        if nick.lower() == "list":
            return "tag syntax has changed. try .tags or .tagged instead"
        elif nick.lower() == "del":
            return 'tag syntax has changed. try ".untag %s" instead' % subject
        return add_tag(db, chan, sanitize(nick), sanitize(subject))
    else:
        tags = get_tags_string_by_nick(db, chan, inp)
        if tags:
            return tags
        else:
            return tag.__doc__

@hook.command
def untag(inp, chan="", db=None):
    ".untag <nick> <tag> -- unmarks <nick> as <tag> {related: .tag, .tags, .tagged, .is}"

    delete = re.match(r"(\S+) (.+)$", inp)

    if delete:
        nick, del_tag = delete.groups()
        return delete_tag(db, chan, nick, del_tag)
    else:
        return untag.__doc__

@hook.command
def tags(inp, chan="", db=None):
    ".tags <nick>/<nick1> <nick2>/list -- get list of tags for <nick>, or a list of tags {related: .tag, .untag, .tagged, .is}"
    if inp == "list":
        return get_tag_counts_by_chan(db, chan)
    elif " " in inp:
        tag_intersect = None
        nicks = [nick.strip() for nick in inp.split(" ")]
        munged_nicks = [munge(x, 1) for x in nicks]
        for nick in nicks:
            curr_tags = get_tags_by_nick(db, chan, nick)

            if not curr_tags:
                return 'nick "%s" not found' % nick

            if tag_intersect is None:
                tag_intersect = set(curr_tags)
            else:
                tag_intersect.intersection_update(curr_tags)

        msg = 'shared tags for nicks "%s"' % winnow(munged_nicks)
        if not tag_intersect:
            return f"no {msg}"
        else:
            return f"{msg}: " + winnow(
                [tag[0] for tag in tag_intersect], 400 - len(msg)
            )

    tags = get_tags_string_by_nick(db, chan, inp)
    if tags:
        return tags
    else:
        return get_nicks_by_tagset(db, chan, inp)

@hook.command
def tagged(inp, chan="", db=None):
    ".tagged <tag> [& tag...] -- get nicks marked as <tag> (separate multiple tags with &) {related: .tag, .untag, .tags, .is}"

    return get_nicks_by_tagset(db, chan, inp)

@hook.command("is")
def is_tagged(inp, chan="", db=None):
    ".is <nick> <tag> -- checks if <nick> has been marked as <tag> {related: .tag, .untag, .tags, .tagged}"

    args = re.match(r"(\S+) (.+)$", inp)

    if args:
        nick, tag = args.groups()
        found = db.execute(
            "select 1 from tag"
            " where lower(nick)=lower(?)"
            "   and lower(subject)=lower(?)"
            "   and chan=?",
            (nick, tag, chan),
        ).fetchone()
        if found:
            return "yes"
        else:
            return "no"
    else:
        return is_tagged.__doc__

def distance(lat1, lon1, lat2, lon2):
    deg_to_rad = old_div(math.pi, 180)
    lat1 *= deg_to_rad
    lat2 *= deg_to_rad
    lon1 *= deg_to_rad
    lon2 *= deg_to_rad

    R = 6371  # km
    d = (
        math.acos(
            math.sin(lat1) * math.sin(lat2)
            + math.cos(lat1) * math.cos(lat2) * math.cos(lon2 - lon1)
        )
        * R
    )
    return d

@hook.command(autohelp=False)
def near(inp, nick="", chan="", db=None):
    try:
        loc = db.execute(
            "select lat, lon from location where chan=? and nick=lower(?)", (chan, nick)
        ).fetchone()
    except db.OperationError:
        loc = None

    if loc is None:
        return "use .weather <loc> first to set your location"

    lat, lon = loc

    db.create_function("distance", 4, distance)
    nearby = db.execute(
        "select nick, distance(lat, lon, ?, ?) as dist from location where chan=?"
        " and nick != lower(?) order by dist limit 20",
        (lat, lon, chan, nick),
    ).fetchall()

    in_miles = "mi" in inp.lower()

    out = "(km) "
    factor = 1.0
    if in_miles:
        out = "(mi) "
        factor = 0.621

    while nearby and len(out) < 200:
        nick, dist = nearby.pop(0)
        out += "%s:%.0f " % (munge(nick, 1), dist * factor)

    return out

character_replacements = {
    "a": "ä",
    #    'b': 'Б',
    "c": "ċ",
    "d": "đ",
    "e": "ë",
    "f": "ƒ",
    "g": "ġ",
    "h": "ħ",
    "i": "í",
    "j": "ĵ",
    "k": "ķ",
    "l": "ĺ",
    #    'm': 'ṁ',
    "n": "ñ",
    "o": "ö",
    "p": "ρ",
    #    'q': 'ʠ',
    "r": "ŗ",
    "s": "š",
    "t": "ţ",
    "u": "ü",
    #    'v': '',
    "w": "ω",
    "x": "χ",
    "y": "ÿ",
    "z": "ź",
    "A": "Å",
    "B": "Β",
    "C": "Ç",
    "D": "Ď",
    "E": "Ē",
    #    'F': 'Ḟ',
    "G": "Ġ",
    "H": "Ħ",
    "I": "Í",
    "J": "Ĵ",
    "K": "Ķ",
    "L": "Ĺ",
    "M": "Μ",
    "N": "Ν",
    "O": "Ö",
    "P": "Р",
    #    'Q': 'Ｑ',
    "R": "Ŗ",
    "S": "Š",
    "T": "Ţ",
    "U": "Ů",
    #    'V': 'Ṿ',
    "W": "Ŵ",
    "X": "Χ",
    "Y": "Ỳ",
    "Z": "Ż",
}

repo_name: rmmh/skybot

repo_language: Python


text:
from builtins import str
import urllib.parse

from util import hook, http

@hook.command
def down(inp):
    """.down <url> -- checks to see if the website is down"""

    # urlparse follows RFC closely, so we have to check for schema existance and prepend empty schema if necessary
    if not inp.startswith("//") and "://" not in inp:
        inp = "//" + inp

    urlp = urllib.parse.urlparse(str(inp), "http")

    if urlp.scheme not in ("http", "https"):
        return inp + " is not a valid HTTP URL"

    inp = "%s://%s" % (urlp.scheme, urlp.netloc)

    # http://mail.python.org/pipermail/python-list/2006-December/589854.html
    try:
        http.get(inp, get_method="HEAD")
        return inp + " seems to be up"
    except http.URLError as error:
        return inp + " seems to be down. Error: %s" % error.reason


text:
import random
import re

from util import hook, http

@hook.command
def suggest(inp, inp_unstripped=None):
    ".suggest [#n] <phrase> -- gets a random/the nth suggested google search"

    if inp_unstripped is not None:
        inp = inp_unstripped
    m = re.match("^#(\d+) (.+)$", inp)
    num = 0
    if m:
        num, inp = m.groups()
        num = int(num)

    json = http.get_json(
        "http://suggestqueries.google.com/complete/search", client="firefox", q=inp
    )
    suggestions = json[1]
    if not suggestions:
        return "no suggestions found"

    if not num:
        num = random.randint(1, len(suggestions))
    if len(suggestions) + 1 <= num:
        return "only got %d suggestions" % len(suggestions)
    out = suggestions[num - 1]
    return "#%d: %s" % (num, out)


text:
"""
remember.py: written by Scaevolus 2010
"""

import re
import string
import unittest

from util import hook

def db_init(db):
    db.execute(
        "create table if not exists memory(chan, word, data, nick,"
        " primary key(chan, word))"
    )
    db.commit()

def get_memory(db, chan, word):
    row = db.execute(
        "select data from memory where chan=? and word=lower(?)", (chan, word)
    ).fetchone()
    if row:
        return row[0]
    else:
        return None

@hook.command
@hook.command("r")
def remember(inp, nick="", chan="", db=None):
    ".remember <word> [+]<data> s/<before>/<after> -- maps word to data in the memory, or "
    " does a string replacement (not regex)"
    db_init(db)

    append = False
    replacement = False

    try:
        head, tail = inp.split(None, 1)
    except ValueError:
        return remember.__doc__

    data = get_memory(db, chan, head)
    if data is not None:
        _head, _tail = data.split(None, 1)
    else:
        _head, _tail = head, ""

    if tail[0] == "+":
        append = True
        # ignore + symbol
        new = tail[1:]
        # data is stored with the input so ignore it when re-adding it
        if len(tail) > 1 and tail[1] in (string.punctuation + " "):
            tail = _tail + new
        else:
            tail = _tail + " " + new

    if len(tail) > 2 and tail[0] == "s" and tail[1] in string.punctuation:
        if _tail == "":
            return "I don't know about that."
        args = tail.split(tail[1])
        if len(args) == 4 and args[3] == "":
            args = args[:-1]
        if len(args) == 3:
            replacement = True
            _, src, dst = args
            new_data = _tail.replace(src, dst, 1)
            if new_data == _tail:
                return "replacement left data unchanged"
            tail = new_data
        else:
            return "invalid replacement syntax -- try s$foo$bar instead?"

    db.execute(
        "replace into memory(chan, word, data, nick) values" " (?,lower(?),?,?)",
        (chan, head, head + " " + tail, nick),
    )
    db.commit()

    if data:
        if append:
            return "appending %s to %s" % (new, data.replace('"', "''"))
        elif replacement:
            return "replacing '%s' with '%s' in %s" % (src, dst, _tail)
        else:
            return 'forgetting "%s", remembering this instead.' % data.replace(
                '"', "''"
            )
    else:
        return "done."

@hook.command
@hook.command("f")
def forget(inp, chan="", db=None):
    ".forget <word> -- forgets the mapping that word had"

    db_init(db)
    data = get_memory(db, chan, inp)

    if data:
        db.execute("delete from memory where chan=? and word=lower(?)", (chan, inp))
        db.commit()
        return "forgot `%s`" % data.replace("`", "'")
    else:
        return "I don't know about that."

def get_page(data, start_idx, min_page_len, max_page_len):
    """
    slices the data string, starting at start_idx, into a chunk no longer
    than max_page_len. The slice will occur at a comma if the resulting
    string would not be shorter than min_page_len.
    returns a tuple of (result str, slice index)
    """
    page_data = data[start_idx : start_idx + max_page_len]

    if len(page_data) < max_page_len:
        return page_data, len(page_data) + start_idx

    last_comma_idx = page_data.rfind(",")

    if last_comma_idx < min_page_len:
        ret = data[start_idx : max_page_len + start_idx]
        return ret, len(ret) + start_idx

    end_idx = last_comma_idx + start_idx
    return data[start_idx:end_idx], end_idx

def get_pages(data, min_page_len, max_page_len):
    "slices the data string into pages using get_page, returning a list"
    result = []

    page_data, last_idx = get_page(data, 0, min_page_len, max_page_len)
    while page_data:
        result.append(page_data)
        page_data, last_idx = get_page(data, last_idx, min_page_len, max_page_len)

    return result

more_pages_message = " (%s page(s) left)"
page_missing_message = "%s only has %s page(s)"
message_len_limit = (
    512 - len(more_pages_message) - 75
)  # fudge factor for protocol overhead

@hook.regex(r"^\? ?(\S+) ?(\d+)?")
def question(inp, chan="", say=None, db=None):
    "?<word> [page] -- shows what data is associated with word, paginated by page (default 1)"
    db_init(db)

    min_page_len = 100

    word = inp.group(1).strip()
    page = int(inp.group(2) or 1)

    if page is None:
        page = 1

    data = get_memory(db, chan, word)
    if data:
        pages = get_pages(data, min_page_len, message_len_limit)
        page_idx = page - 1  # 1-indexed
        try:
            page_data = pages[page_idx]
        except IndexError:
            say(page_missing_message % (word, len(pages)))
            return

        page_data = page_data.lstrip(", ")

        last_page = len(pages) - 1
        if page_idx == last_page:
            say(page_data)
            return

        pages_left = last_page - page_idx
        say(page_data + (more_pages_message % int(pages_left)))

class MemoryTest(unittest.TestCase):
    def setUp(self):
        import sqlite3

        self.db = sqlite3.connect(":memory:")

    def remember(self, inp, nick="someone", chan="#test"):
        return remember(inp, nick=nick, chan=chan, db=self.db)

    def forget(self, inp, chan="#test"):
        return forget(inp, chan=chan, db=self.db)

    def question(self, inp, chan="#test"):
        output = []
        question(
            re.match(r"(\S+) ?(\d+)?", inp), chan=chan, say=output.append, db=self.db
        )
        return output[0] if output else None

    def test_remember(self):
        assert "done." == self.remember("dogs :3")
        assert "dogs :3" == self.question("dogs")

    def test_remember_doc(self):
        assert ".remember <word>" in self.remember("bad_syntax")

    def test_remember_overwrite(self):
        self.remember("dogs :(")
        assert 'forgetting "dogs :("' in self.remember("dogs :3")
        assert "dogs :3" == self.question("dogs")

    def test_remember_hygiene(self):
        self.remember("python good", chan="#python")
        self.remember("python bad", chan="#ruby")
        assert "python good" == self.question("python", "#python")
        assert "python bad" == self.question("python", "#ruby")

    def test_remember_append(self):
        self.remember("ball big")
        self.remember("ball +red")
        assert "ball big red" == self.question("ball")

    def test_remember_append_punctuation(self):
        self.remember("baby young")
        self.remember("baby +, hungry")
        assert "baby young, hungry" == self.question("baby")

    def test_remember_replace(self):
        self.remember("person is very rich (rich!)")
        self.remember("person s/rich/poor/")
        assert "person is very poor (rich!)" == self.question("person")

    def test_remember_replace_invalid(self):
        self.remember("fact bar")
        assert "invalid replacement" in self.remember("fact s/too/many/seps/!")
        assert "invalid replacement" in self.remember("fact s/toofew")

    def test_remember_replace_ineffective(self):
        self.remember("hay stack")
        assert "unchanged" in self.remember("hay s:needle:shiny needle")

    def test_remember_replace_missing(self):
        assert "I don't know about that" in self.remember("hay s/what/lol")

    def test_question_empty(self):
        assert self.question("not_in_db") is None

    def test_forget(self):
        self.remember("meat good", chan="#carnivore")
        self.remember("meat bad", chan="#vegan")
        assert "forgot `meat good`" in self.forget("meat", chan="#carnivore")
        assert "meat bad" == self.question("meat", chan="#vegan")

    def test_forget_missing(self):
        assert "don't know" in self.forget("fakekey")

    def test_get_page(self):
        assert get_page("123456789", 0, 5, 8) == ("12345678", 8)  # max length
        assert get_page("123,456789", 0, 5, 8) == (
            "123,4567",
            8,
        )  # min length not satisfied
        assert get_page("123,45,67,89", 0, 5, 8) == (
            "123,45",
            6,
        )  # chooses correct comma
        assert get_page("", 0, 5, 8) == ("", 0)  # correct empty result
        # start offsets
        assert get_page("123456789", 1, 5, 9) == ("23456789", 9)
        assert get_page("123456789", 1, 5, 8) == ("23456789", 9)
        assert get_page("1234567,89", 1, 5, 8) == ("234567", 7)
        assert get_page("", 1, 5, 8) == ("", 1)

    def test_get_pages(self):
        assert get_pages("123456789", 5, 8) == ["12345678", "9"]
        assert get_pages("123,456789", 5, 8) == ["123,4567", "89"]
        assert get_pages("123,45,67,89", 5, 8) == ["123,45", ",67,89"]
        assert get_pages("", 5, 8) == []

    def test_paging_message(self):
        long_string = "long " + "a" * 1000

        self.remember(long_string, chan="#long")
        assert more_pages_message % 2 in self.question(
            "long", chan="#long"
        )  # "2 pages left"
        assert more_pages_message % 1 in self.question("long 2", chan="#long")
        assert not re.match(r"\d", self.question("long 3", chan="#long"))

    def test_paging_result(self):
        long_string = "long " + "x" * message_len_limit + "y" * message_len_limit

        self.remember(long_string, chan="#long")
        p1_data = self.question("long", chan="#long")
        p2_data = self.question("long 2", chan="#long")
        p3_data = self.question("long 3", chan="#long")

        all_data = p1_data + p2_data + p3_data

        # no data lost
        assert all_data.count("x") == message_len_limit
        assert all_data.count("y") == message_len_limit

    def test_missing_page(self):
        long_string = "long " + "a" * 1000

        self.remember("long " + long_string)
        self.remember("thing foo")

        assert page_missing_message % ("thing", 1) in self.question("thing 2")
        assert page_missing_message % ("long", 3) in self.question("long 11")

    def test_strip_leading_comma(self):
        long_string = (
            "long "
            + "x" * (message_len_limit - 10)
            + ", "
            + "y" * (message_len_limit - 10)
        )
        self.remember(long_string)
        assert ", " not in self.question("long 2")

    def test_regressions(self):
        # improper split on last comma even though full message is < length limit
        gmg = "gmg good morning goons, goodly morning goons, gob morning goons, good moring Gobiner, good morning gobs, goom morgan goobs"
        self.remember(gmg)
        assert self.question(gmg) == gmg

if __name__ == "__main__":
    unittest.main()


text:
"""Searches wikipedia and returns first sentence of article
Scaevolus 2009"""

from __future__ import unicode_literals

import re

from util import hook, http

OUTPUT_LIMIT = 240  # character limit
INSTANCES = {
    "encyclopediadramatica": {
        "name": "Encyclopedia Dramatica",
        "search": "https://encyclopediadramatica.wiki/api.php",
        "regex": r"(https?://encyclopediadramatica\.wiki/index\.php/[^ ]+)",
    },
    "wikipedia_en": {
        "name": "Wikipedia",
        "search": "https://en.wikipedia.org/w/api.php",
        "regex": r"(https?://en\.wikipedia\.org/wiki/[^ ]+)",
    },
}

def search(instance, query):
    if instance not in INSTANCES:
        return

    wiki = INSTANCES[instance]
    search = http.get_json(
        wiki["search"],
        query_params={
            "action": "opensearch",
            "format": "json",
            "limit": 2,
            "search": query,
        },
    )

    print(search)

    titles = search[1]
    descriptions = search[2]
    urls = search[3]

    return (titles, descriptions, urls)

def scrape_text(url):
    h = http.get_html(url)

    title = h.xpath("string(//h1[@id='firstHeading'])")
    body = h.xpath("//div[@id='mw-content-text']/descendant-or-self::p")

    if title:
        title = title.strip()

    if body is None:
        return "Error reading the article"

    output = []

    for paragraph in body:
        text = paragraph.text_content()
        if len(text) > 4:  # skip empty paragraphs
            output.append(text)

    output = " ".join(output)

    return output, title

def command_wrapper(instance, inp):
    titles, descriptions, urls = search(instance, inp)

    if not titles:
        return "No results found."

    title = titles[0]
    url = urls[0]

    # `real_title` shows the article title after a
    # redirect. its generally longer than `title`
    print(url)
    output, real_title = scrape_text(url)

    if len(output) > OUTPUT_LIMIT:
        output = output[:OUTPUT_LIMIT] + "..."

    if title == real_title:
        return "\x02{} -\x02 {} \x02-\x02 {}".format(title, output, url)
    else:
        return "\x02{} -\x02 {} \x02-\x02 {} (redirected from {})".format(
            real_title, output, url, title
        )

def url_wrapper(instance, url):
    output, title = scrape_text(url)

    if len(output) > OUTPUT_LIMIT:
        output = output[:OUTPUT_LIMIT] + "..."

    return "\x02{} -\x02 {}".format(title, output)

@hook.command("ed")
@hook.command
def drama(inp):
    "drama <article> -- search an Encyclopedia Dramatica article"
    return command_wrapper("encyclopediadramatica", inp)

@hook.command("w")
@hook.command
def wikipedia(inp):
    "wikipedia <article> -- search a wikipedia article"
    return command_wrapper("wikipedia_en", inp)


text:
from __future__ import unicode_literals

import math
import random
import re
import string
from time import strptime, strftime
from urllib.parse import quote

from util import hook, http

b36_alphabet = string.digits + string.ascii_lowercase

def base36(x):
    frac, x = math.modf(x)
    r = []
    x = int(x)
    f = []
    # fractional part translated from V8 DoubleToRadixCString
    delta = max(0.5 * (math.nextafter(x, x+1) - x), math.nextafter(0, 1))
    while frac >= delta:
        frac *= 36
        delta *= 36
        d = int(frac)
        f.append(d)
        frac -= d
        if frac > .5 or frac == .5 and d & 1:
            if frac + delta > 1:
                i = len(f)
                while True:
                    i -= 1
                    if i == -1:
                        x += 1
                        break
                    if f[i] + 1 < 36:
                        f[i] += 1
                        break
    f = [b36_alphabet[x] for x in f]
    while x:
        x, rem = divmod(x, 36)
        r.append(b36_alphabet[rem])

    if f:
        return ''.join(r[::-1]) + '.' + ''.join(f)
    else:
        return ''.join(r[::-1])

@hook.command
def twitter(inp):
    ".twitter <id> -- get tweet <id>"

    if re.match(r"^\d+$", inp):
        x = int(inp)/1e15*math.pi
        token = re.sub(r'0+|\.', '', base36(x))
        request_url = "https://cdn.syndication.twimg.com/tweet-result?lang=en"
    else:
        return 'error: can only get tweets by id'

    try:
        tweet = http.get_json(request_url, id=inp, token=token)
    except http.HTTPError as e:
        errors = {
            400: "bad request (ratelimited?)",
            401: "unauthorized",
            403: "forbidden",
            404: "invalid user/id",
            500: "twitter is broken",
            502: 'twitter is down ("getting upgraded")',
            503: "twitter is overloaded (lol, RoR)",
            410: "twitter shut off api v1.",
        }
        if e.code == 404:
            return "error: invalid tweet id"
        if e.code in errors:
            return "error: " + errors[e.code]
        return "error: unknown %s" % e.code

    if "retweeted_status" in tweet:
        rt = tweet["retweeted_status"]
        rt_text = http.unescape(rt["full_text"]).replace("\n", " ")
        text = "RT @%s %s" % (rt["user"]["screen_name"], rt_text)
    else:
        text = http.unescape(tweet["text"]).replace("\n", " ")
        for url in tweet.get('entities', {}).get('urls', []):
            new_text = text.replace(url['url'], url['expanded_url'])
            if len(new_text) < 350:
                text = new_text
        for url in tweet.get('mediaDetails', []) + tweet.get('entities', {}).get('media', []):
            if url.get('type') in ('video', 'animated_gif'):
                try:
                    media_url = max(url['video_info']['variants'], key=lambda x: x.get('bitrate', 0))['url']
                except KeyError:
                    continue
            elif 'media_url_https' in url:
                media_url = url['media_url_https']
            else:
                continue
            if url['url'] in text:
                new_text = text.replace(url['url'], media_url)
            else:
                new_text = text + ' ' + media_url
            if len(new_text) < 400:
                text = new_text
    screen_name = tweet["user"]["screen_name"]
    time = tweet["created_at"]

    time = strftime("%Y-%m-%d %H:%M:%S", strptime(time, "%Y-%m-%dT%H:%M:%S.000Z"))

    return "%s \x02%s\x02: %s" % (time, screen_name, text)

@hook.regex(r"https?://(mobile\.)?twitter.com/(#!/)?([_0-9a-zA-Z]+)/status/(?P<id>\d+)")
def show_tweet(match):
    return twitter(match.group("id"))


text:
import hashlib

from util import hook

@hook.command("md5")
def hash_md5(inp):
    return hashlib.md5(inp.encode("utf-8")).hexdigest()

@hook.command("sha1")
def hash_sha1(inp):
    return hashlib.sha1(inp.encode("utf-8")).hexdigest()

@hook.command("sha256")
def hash_sha256(inp):
    return hashlib.sha256(inp.encode("utf-8")).hexdigest()

@hook.command
def hash(inp):
    """.hash <text> -- returns hashes of <text>"""

    hashes = [
        (x, hashlib.__dict__[x](inp.encode("utf-8")).hexdigest())
        for x in "md5 sha1 sha256".split()
    ]

    return ", ".join("%s: %s" % i for i in hashes)


text:
from __future__ import print_function
from builtins import range
from util import hook, http
import random

def card_search(name):
    matching_cards = http.get_json(
        "https://api.magicthegathering.io/v1/cards", name=name
    )
    for card in matching_cards["cards"]:
        if card["name"].lower() == name.lower():
            return card
    return random.choice(matching_cards["cards"])

@hook.command
def mtg(inp, say=None):
    """.mtg <name> - Searches for Magic the Gathering card given <name>"""

    try:
        card = card_search(inp)
    except IndexError:
        return "Card not found."
    symbols = {
        "{0}": "0",
        "{1}": "1",
        "{2}": "2",
        "{3}": "3",
        "{4}": "4",
        "{5}": "5",
        "{6}": "6",
        "{7}": "7",
        "{8}": "8",
        "{9}": "9",
        "{10}": "10",
        "{11}": "11",
        "{12}": "12",
        "{13}": "13",
        "{14}": "14",
        "{15}": "15",
        "{16}": "16",
        "{17}": "17",
        "{18}": "18",
        "{19}": "19",
        "{20}": "20",
        "{T}": "\u27F3",
        "{S}": "\u2744",
        "{Q}": "\u21BA",
        "{C}": "\u27E1",
        "{W}": "W",
        "{U}": "U",
        "{B}": "B",
        "{R}": "R",
        "{G}": "G",
        "{W/P}": "\u03D5",
        "{U/P}": "\u03D5",
        "{B/P}": "\u03D5",
        "{R/P}": "\u03D5",
        "{G/P}": "\u03D5",
        "{X}": "X",
        "\n": " ",
    }
    results = {
        "name": card["name"],
        "type": card["type"],
        "cost": card.get("manaCost", ""),
        "text": card.get("text", ""),
        "power": card.get("power"),
        "toughness": card.get("toughness"),
        "loyalty": card.get("loyalty"),
        "multiverseid": card.get("multiverseid"),
    }

    for fragment, rep in symbols.items():
        results["text"] = results["text"].replace(fragment, rep)
        results["cost"] = results["cost"].replace(fragment, rep)

    template = ["{name} -"]
    template.append("{type}")
    template.append("- {cost} |")
    if results["loyalty"]:
        template.append("{loyalty} Loyalty |")
    if results["power"]:
        template.append("{power}/{toughness} |")
    template.append(
        "{text} | http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid={multiverseid}"
    )

    return " ".join(template).format(**results)

if __name__ == "__main__":
    print(card_search("Black Lotus"))
    print(mtg("Black Lotus"))


text:
from __future__ import unicode_literals

from builtins import str
import re
import time

from util import hook, http

youtube_re = (
    r"(?:youtube.*?(?:v=|/v/|/shorts/)|youtu\.be/|yooouuutuuube.*?id=)" "([-_a-z0-9]+)",
    re.I,
)

BASE_URL = "https://www.googleapis.com/youtube/v3/"
INFO_URL = BASE_URL + "videos?part=snippet,contentDetails,statistics&hl=en"
SEARCH_API_URL = BASE_URL + "search"
VIDEO_URL = "https://youtube.com/watch?v=%s"

def get_video_description(vid_id, api_key):
    j = http.get_json(INFO_URL, id=vid_id, key=api_key)

    if not j["pageInfo"]["totalResults"]:
        return

    j = j["items"][0]

    duration = j["contentDetails"]["duration"].replace("PT", "").lower()

    published = j["snippet"]["publishedAt"].replace(".000Z", "Z")
    published = time.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
    published = time.strftime("%Y.%m.%d", published)

    views = group_int_digits(j["statistics"]["viewCount"], ",")
    likes = j["statistics"].get("likeCount", 0)
    dislikes = j["statistics"].get("dislikeCount", 0)
    title = j["snippet"]["title"]
    if "localized" in j["snippet"]:
        title = j["snippet"]["localized"].get("title") or title

    out = (
        "\x02{title}\x02 - length \x02{duration}\x02 - "
        "{likes}\u2191{dislikes}\u2193 - "
        "\x02{views}\x02 views - "
        "\x02{snippet[channelTitle]}\x02 on \x02{published}\x02"
    ).format(
        duration=duration,
        likes=likes,
        dislikes=dislikes,
        views=views,
        published=published,
        title=title,
        **j
    )

    # TODO: figure out how to detect NSFW videos

    return out

def group_int_digits(number, delimiter=" ", grouping=3):
    base = str(number).strip()
    builder = []
    while base:
        builder.append(base[-grouping:])
        base = base[:-grouping]
    builder.reverse()
    return delimiter.join(builder)

@hook.api_key("google")
@hook.regex(*youtube_re)
def youtube_url(match, api_key=None):
    return get_video_description(match.group(1), api_key)

@hook.api_key("google")
@hook.command("yt")
@hook.command("y")
@hook.command
def youtube(inp, api_key=None):
    ".youtube <query> -- returns the first YouTube search result for <query>"

    params = {
        "key": api_key,
        "fields": "items(id(videoId))",
        "part": "snippet",
        "type": "video",
        "maxResults": "1",
        "q": inp,
    }

    j = http.get_json(SEARCH_API_URL, **params)

    if "error" in j:
        return "error while performing the search"

    results = j.get("items")

    if not results:
        return "no results found"

    vid_id = j["items"][0]["id"]["videoId"]

    return get_video_description(vid_id, api_key) + " - " + VIDEO_URL % vid_id


text:
from __future__ import print_function, unicode_literals

# metacritic.com scraper

import re
from urllib.error import HTTPError

from util import hook, http

SCORE_CLASSES = {
    "\x0304": ["negative", "bad", "score_terrible", "score_unfavorable"],
    "\x0308": ["mixed", "forty", "fifty", "score_mixed"],
    "\x0303": [
        "sixtyone",
        "seventyfive",
        "good",
        "positive",
        "score_favorable",
        "score_outstanding",
    ],
}

def get_score_color(element_classes):
    for (color, score_classes) in SCORE_CLASSES.items():
        for score_class in score_classes:
            if score_class in element_classes:
                return color

@hook.command("mc")
def metacritic(inp):
    ".mc [all|movie|tv|album|x360|ps3|pc|gba|ds|3ds|wii|vita|wiiu|xone|ps4] <title> -- gets rating for" " <title> from metacritic on the specified medium"

    # if the results suck, it's metacritic's fault

    args = inp.strip()

    game_platforms = (
        "x360",
        "ps3",
        "pc",
        "gba",
        "ds",
        "3ds",
        "wii",
        "vita",
        "wiiu",
        "xone",
        "ps4",
    )
    all_platforms = game_platforms + ("all", "movie", "tv", "album")

    try:
        plat, title = args.split(" ", 1)
        if plat not in all_platforms:
            # raise the ValueError so that the except block catches it
            # in this case, or in the case of the .split above raising the
            # ValueError, we want the same thing to happen
            raise ValueError
    except ValueError:
        plat = "all"
        title = args

    cat = "game" if plat in game_platforms else plat

    title_safe = http.quote_plus(title)

    url = "http://www.metacritic.com/search/%s/%s/results" % (cat, title_safe)

    print(url)

    try:
        doc = http.get_html(url)
    except HTTPError:
        return "error fetching results"

    # get the proper result element we want to pull data from

    result = None

    if not doc.find_class("query_results"):
        return "no results found"

    # if they specified an invalid search term, the input box will be empty
    if doc.get_element_by_id("primary_search_box").value == "":
        return "invalid search term"

    if plat not in game_platforms:
        # for [all] results, or non-game platforms, get the first result
        result = doc.find_class("result first_result")[0]

        # find the platform, if it exists
        result_type = result.find_class("result_type")
        if result_type:

            # if the result_type div has a platform div, get that one
            platform_div = result_type[0].find_class("platform")
            if platform_div:
                plat = platform_div[0].text_content().strip()
            else:
                # otherwise, use the result_type text_content
                plat = result_type[0].text_content().strip()

    else:
        # for games, we want to pull the first result with the correct
        # platform
        results = doc.find_class("result")
        for res in results:
            result_plat = res.find_class("platform")[0].text_content().strip()
            if result_plat == plat.upper():
                result = res
                break

    if not result:
        return "no results found"

    # get the name, release date, and score from the result
    product_title_element = result.find_class("product_title")[0]

    review = {
        "platform": plat.upper(),
        "title": product_title_element.text_content().strip(),
        "link": "http://metacritic.com"
        + product_title_element.find("a").attrib["href"],
    }

    try:
        score_element = result.find_class("metascore_w")[0]

        review["score"] = score_element.text_content().strip()

        review["score_color"] = get_score_color(score_element.classes)
    except IndexError:
        review["score"] = "unknown"

    return "[{platform}] {title} - \x02{score_color}{score}\x0f - {link}".format(
        **review
    )


text:
"""
A Google API key is required and retrieved from the bot config file.
Since December 1, 2011, the Google Translate API is a paid service only.
"""

from builtins import zip
from builtins import chr
import html.entities
import re

from util import hook, http

########### from http://effbot.org/zone/re-sub.htm#unescape-html #############

def unescape(text):
    def fixup(m):
        text = m.group(0)
        if text[:2] == "&#":
            # character reference
            try:
                if text[:3] == "&#x":
                    return chr(int(text[3:-1], 16))
                else:
                    return chr(int(text[2:-1]))
            except ValueError:
                pass
        else:
            # named entity
            try:
                text = chr(html.entities.name2codepoint[text[1:-1]])
            except KeyError:
                pass
        return text  # leave as is

    return re.sub("&#?\w+;", fixup, text)

##############################################################################

def goog_trans(api_key, text, slang, tlang):
    url = "https://www.googleapis.com/language/translate/v2"
    parsed = http.get_json(url, key=api_key, q=text, source=slang, target=tlang)
    if not 200 <= parsed["responseStatus"] < 300:
        raise IOError(
            "error with the translation server: %d: %s"
            % (parsed["responseStatus"], parsed["responseDetails"])
        )
    if not slang:
        return unescape(
            "(%(detectedSourceLanguage)s) %(translatedText)s"
            % (parsed["responseData"]["data"]["translations"][0])
        )
    return unescape(
        "%(translatedText)s" % parsed["responseData"]["data"]["translations"][0]
    )

def match_language(fragment):
    fragment = fragment.lower()
    for short, _ in lang_pairs:
        if fragment in short.lower().split():
            return short.split()[0]

    for short, full in lang_pairs:
        if fragment in full.lower():
            return short.split()[0]

    return None

@hook.api_key("google")
@hook.command
def translate(inp, api_key=""):
    ".translate [source language [target language]] <sentence> -- translates" " <sentence> from source language (default autodetect) to target" " language (default English) using Google Translate"

    args = inp.split(" ", 2)

    try:
        if len(args) >= 2:
            sl = match_language(args[0])
            if not sl:
                return goog_trans(api_key, inp, "", "en")
            if len(args) == 2:
                return goog_trans(api_key, args[1], sl, "en")
            if len(args) >= 3:
                tl = match_language(args[1])
                if not tl:
                    if sl == "en":
                        return "unable to determine desired target language"
                    return goog_trans(api_key, args[1] + " " + args[2], sl, "en")
                return goog_trans(api_key, args[2], sl, tl)
        return goog_trans(api_key, inp, "", "en")
    except IOError as e:
        return e

languages = "ja fr de ko ru zh".split()
language_pairs = zip(languages[:-1], languages[1:])

def babel_gen(inp):
    for language in languages:
        inp = inp.encode("utf8")
        trans = goog_trans(api_key, inp, "en", language).encode("utf8")
        inp = goog_trans(api_key, trans, language, "en")
        yield language, trans, inp

@hook.api_key("google")
@hook.command
def babel(inp, api_key=""):
    ".babel <sentence> -- translates <sentence> through multiple languages"

    try:
        return list(babel_gen(api_key, inp))[-1][2]
    except IOError as e:
        return e

@hook.api_key("google")
@hook.command
def babelext(inp, api_key=""):
    ".babelext <sentence> -- like .babel, but with more detailed output"

    try:
        babels = list(babel_gen(api_key, inp))
    except IOError as e:
        return e

    out = ""
    for lang, trans, text in babels:
        out += '%s:"%s", ' % (lang, text.decode("utf8"))

    out += 'en:"' + babels[-1][2].decode("utf8") + '"'

    if len(out) > 300:
        out = out[:150] + " ... " + out[-150:]

    return out

lang_pairs = [
    ("no", "Norwegian"),
    ("it", "Italian"),
    ("ht", "Haitian Creole"),
    ("af", "Afrikaans"),
    ("sq", "Albanian"),
    ("ar", "Arabic"),
    ("hy", "Armenian"),
    ("az", "Azerbaijani"),
    ("eu", "Basque"),
    ("be", "Belarusian"),
    ("bg", "Bulgarian"),
    ("ca", "Catalan"),
    ("zh-CN zh", "Chinese"),
    ("hr", "Croatian"),
    ("cs", "Czech"),
    ("da", "Danish"),
    ("nl", "Dutch"),
    ("en", "English"),
    ("et", "Estonian"),
    ("tl", "Filipino"),
    ("fi", "Finnish"),
    ("fr", "French"),
    ("gl", "Galician"),
    ("ka", "Georgian"),
    ("de", "German"),
    ("el", "Greek"),
    ("ht", "Haitian Creole"),
    ("iw", "Hebrew"),
    ("hi", "Hindi"),
    ("hu", "Hungarian"),
    ("is", "Icelandic"),
    ("id", "Indonesian"),
    ("ga", "Irish"),
    ("it", "Italian"),
    ("ja jp jpn", "Japanese"),
    ("ko", "Korean"),
    ("lv", "Latvian"),
    ("lt", "Lithuanian"),
    ("mk", "Macedonian"),
    ("ms", "Malay"),
    ("mt", "Maltese"),
    ("no", "Norwegian"),
    ("fa", "Persian"),
    ("pl", "Polish"),
    ("pt", "Portuguese"),
    ("ro", "Romanian"),
    ("ru", "Russian"),
    ("sr", "Serbian"),
    ("sk", "Slovak"),
    ("sl", "Slovenian"),
    ("es", "Spanish"),
    ("sw", "Swahili"),
    ("sv", "Swedish"),
    ("th", "Thai"),
    ("tr", "Turkish"),
    ("uk", "Ukrainian"),
    ("ur", "Urdu"),
    ("vi", "Vietnamese"),
    ("cy", "Welsh"),
    ("yi", "Yiddish"),
]


text:
"""brainfuck interpreter adapted from (public domain) code at
http://brainfuck.sourceforge.net/brain.py"""

from builtins import chr
from builtins import range
import re
import random
import unittest

from util import hook

@hook.command
def bf(inp, max_steps=1000000, buffer_size=5000):
    ".bf <prog> -- executes brainfuck program <prog>" ""

    program = re.sub("[^][<>+-.,]", "", inp)

    # create a dict of brackets pairs, for speed later on
    brackets = {}
    open_brackets = []
    for pos in range(len(program)):
        if program[pos] == "[":
            open_brackets.append(pos)
        elif program[pos] == "]":
            if len(open_brackets) > 0:
                brackets[pos] = open_brackets[-1]
                brackets[open_brackets[-1]] = pos
                open_brackets.pop()
            else:
                return "unbalanced brackets"
    if len(open_brackets) != 0:
        return "unbalanced brackets"

    # now we can start interpreting
    ip = 0  # instruction pointer
    mp = 0  # memory pointer
    steps = 0
    memory = [0] * buffer_size  # initial memory area
    rightmost = 0
    output = ""  # we'll save the output here

    # the main program loop:
    while ip < len(program):
        c = program[ip]
        if c == "+":
            memory[mp] = (memory[mp] + 1) % 256
        elif c == "-":
            memory[mp] = (memory[mp] - 1) % 256
        elif c == ">":
            mp += 1
            if mp > rightmost:
                rightmost = mp
                if mp >= len(memory):
                    # no restriction on memory growth!
                    memory.extend([0] * buffer_size)
        elif c == "<":
            mp = (mp - 1) % len(memory)
        elif c == ".":
            output += chr(memory[mp])
            if len(output) > 500:
                break
        elif c == ",":
            memory[mp] = random.randint(1, 255)
        elif c == "[":
            if memory[mp] == 0:
                ip = brackets[ip]
        elif c == "]":
            if memory[mp] != 0:
                ip = brackets[ip]

        ip += 1
        steps += 1
        if steps > max_steps:
            if output == "":
                output = "no output"
            output += " [exceeded %d iterations]" % max_steps
            break

    stripped_output = re.sub(r"[\x00-\x1F]", "", output)

    if stripped_output == "":
        if output != "":
            return "no printable output"
        return "no output"

    return stripped_output[:430]


text:
import os
import re

from util import hook

@hook.command(autohelp=False)
def mem(inp):
    ".mem -- returns bot's current memory usage -- linux/windows only"

    if os.name == "posix":
        status_file = open("/proc/%d/status" % os.getpid()).read()
        line_pairs = re.findall(r"^(\w+):\s*(.*)\s*$", status_file, re.M)
        status = dict(line_pairs)
        keys = "VmSize VmLib VmData VmExe VmRSS VmStk".split()
        return ", ".join(key + ":" + status[key] for key in keys)

    elif os.name == "nt":
        cmd = 'tasklist /FI "PID eq %s" /FO CSV /NH' % os.getpid()
        out = os.popen(cmd).read()

        total = 0
        for amount in re.findall(r"([,0-9]+) K", out):
            total += int(amount.replace(",", ""))

        return "memory usage: %d kB" % total

    return mem.__doc__


text:
from __future__ import unicode_literals

import json
import re

from util import http, hook

MOVIE_SEARCH_URL = "https://www.rottentomatoes.com/api/private/v2.0/search/"
MOVIE_PAGE_URL = "https://www.rottentomatoes.com/m/%s"

def get_rottentomatoes_data(movie_id):
    if movie_id.startswith("/m/"):
        movie_id = movie_id[3:]

    document = http.get_html(MOVIE_PAGE_URL % movie_id)

    # JSON for the page is stored in the script with ID 'jsonLdSchema'
    # So we can pull that for tons of information.
    ld_schema_element = document.xpath("//script[@type='application/ld+json']")[0]
    ld_schema = json.loads(ld_schema_element.text_content())

    scripts = "\n".join(document.xpath("//script/text()"))
    score_info = json.loads(re.search(r"scoreInfo = (.*);", scripts).group(1))[
        "tomatometerAllCritics"
    ]

    try:
        audience_score = document.xpath(
            '//span[contains(@class, "audience") and contains(@class, "rating")]/text()'
        )[0].strip()
    except IndexError:
        audience_score = ""

    return {
        "title": ld_schema["name"],
        "critics_score": score_info["score"],
        "audience_score": audience_score,
        "fresh": score_info["freshCount"],
        "rotten": score_info["rottenCount"],
        "url": MOVIE_PAGE_URL % movie_id,
    }

def search_rottentomatoes(inp):
    results = http.get_json(MOVIE_SEARCH_URL, limit=1, q=inp)

    if not results or not results["movieCount"]:
        return None

    return results["movies"][0]["url"]

@hook.command("rt")
@hook.command
def rottentomatoes(inp):
    ".rt <title> -- gets ratings for <title> from Rotten Tomatoes"

    movie_id = search_rottentomatoes(inp)

    if not movie_id:
        return "no results"

    movie = get_rottentomatoes_data(movie_id)

    return (
        "{title} - critics: \x02{critics_score}%\x02 "
        "({fresh}\u2191{rotten}\u2193) "
        "audience: \x02{audience_score}\x02 - {url}"
    ).format(**movie)


text:
# IMDb lookup plugin by Ghetto Wizard (2011).
from __future__ import unicode_literals

from util import hook, http

# http://www.omdbapi.com/apikey.aspx
@hook.api_key("omdbapi")
@hook.command
def imdb(inp, api_key=None):
    """.imdb <movie> -- gets information about <movie> from IMDb"""

    if not api_key:
        return None

    content = http.get_json("https://www.omdbapi.com/", t=inp, apikey=api_key)

    if content["Response"] == "Movie Not Found":
        return "movie not found"
    elif content["Response"] == "True":
        content["URL"] = "http://www.imdb.com/title/%(imdbID)s" % content

        out = "\x02%(Title)s\x02 (%(Year)s) (%(Genre)s): %(Plot)s"
        if content["Runtime"] != "N/A":
            out += " \x02%(Runtime)s\x02."
        if content["imdbRating"] != "N/A" and content["imdbVotes"] != "N/A":
            out += " \x02%(imdbRating)s/10\x02 with \x02%(imdbVotes)s\x02 votes."
        out += " %(URL)s"
        return out % content
    else:
        return "unknown error"


text:
"""
dice.py: written by Scaevolus 2008, updated 2009
simulates dicerolls
"""
from builtins import str
from builtins import map
from builtins import range
import re
import random
import unittest

from util import hook

whitespace_re = re.compile(r"\s+")
valid_diceroll = r"^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$"
valid_diceroll_re = re.compile(valid_diceroll, re.I)
sign_re = re.compile(r"[+-]?(?:\d*d)?(?:\d+|F)", re.I)
split_re = re.compile(r"([\d+-]*)d?(F|\d*)", re.I)

def nrolls(count, n):
    "roll an n-sided die count times"
    if n == "F":
        return [random.randint(-1, 1) for x in range(min(count, 100))]
    if n < 2:  # it's a coin
        if count < 5000:
            return [random.randint(0, 1) for x in range(count)]
        else:  # fake it
            return [int(random.normalvariate(0.5 * count, (0.75 * count) ** 0.5))]
    else:
        if count < 5000:
            return [random.randint(1, n) for x in range(count)]
        else:  # fake it
            return [
                int(
                    random.normalvariate(
                        0.5 * (1 + n) * count,
                        (((n + 1) * (2 * n + 1) / 6.0 - (0.5 * (1 + n)) ** 2) * count)
                        ** 0.5,
                    )
                )
            ]

@hook.command("roll")
# @hook.regex(valid_diceroll, re.I)
@hook.command
def dice(inp):
    ".dice <diceroll> -- simulates dicerolls, e.g. .dice 2d20-d5+4 roll 2 " "D20s, subtract 1D5, add 4"

    try:  # if inp is a re.match object...
        (inp, desc) = inp.groups()
    except AttributeError:
        (inp, desc) = valid_diceroll_re.match(inp).groups()

    if "d" not in inp:
        return

    spec = whitespace_re.sub("", inp)
    if not valid_diceroll_re.match(spec):
        return "Invalid diceroll"
    groups = sign_re.findall(spec)

    total = 0
    rolls = []

    for roll in groups:
        count, side = split_re.match(roll).groups()
        if count in ["", " ", "+", "-"]:
            count = int("%s1" % count)
        else:
            count = int(count)

        if side.upper() == "F":  # fudge dice are basically 1d3-2
            for fudge in nrolls(count, "F"):
                if fudge == 1:
                    rolls.append("\x033+\x0F")
                elif fudge == -1:
                    rolls.append("\x034-\x0F")
                else:
                    rolls.append("0")
                total += fudge
        elif side == "":
            total += count
        else:
            side = int(side)
            try:
                if count > 0:
                    dice = nrolls(count, side)
                    rolls += list(map(str, dice))
                    total += sum(dice)
                else:
                    dice = nrolls(-count, side)
                    rolls += [str(-x) for x in dice]
                    total -= sum(dice)
            except OverflowError:
                return "Thanks for overflowing a float, jerk >:["

    if desc:
        return "%s: %d (%s=%s)" % (desc.strip(), total, inp, ", ".join(rolls))
    else:
        return "%d (%s=%s)" % (total, inp, ", ".join(rolls))

class DiceTest(unittest.TestCase):
    def test_complex_roll_with_subtraction(self):
        actual = dice("2d20-d5+4")

        match = re.match(
            "(?P<result>\d+) \(2d20-d5\+4=(?P<a>\d+), (?P<b>\d+), -(?P<c>\d+)\)", actual
        )

        assert match is not None

        expected_result = (
            int(match.group("a")) + int(match.group("b")) - int(match.group("c")) + 4
        )

        assert expected_result == int(match.group("result"))


text:
#!/usr/bin/env python3

from __future__ import division, unicode_literals, print_function
from past.utils import old_div
import re

from util import hook, http

def human_price(x):
    if x > 1e9:
        return "{:,.2f}B".format(old_div(x, 1e9))
    elif x > 1e6:
        return "{:,.2f}M".format(old_div(x, 1e6))
    return "{:,.0f}".format(x)

@hook.api_key("iexcloud")
@hook.command
def stock(inp, api_key=None):
    """.stock <symbol> [info] -- retrieves a weeks worth of stats for given symbol. Optionally displays information about the company."""

    if not api_key:
        return "missing api key"

    arguments = inp.split(" ")

    symbol = arguments[0].upper()

    try:
        quote = http.get_json(
            "https://cloud.iexapis.com/stable/stock/{symbol}/quote".format(
                symbol=symbol
            ),
            token=api_key,
        )
    except http.HTTPError:
        return "{} is not a valid stock symbol.".format(symbol)

    if (
        quote["extendedPriceTime"]
        and quote["latestUpdate"] < quote["extendedPriceTime"]
    ):
        price = quote["extendedPrice"]
        change = quote["extendedChange"]
    elif quote["latestSource"] == "Close" and quote.get("iexRealtimePrice"):
        # NASDAQ stocks don't have extendedPrice anymore :(
        price = quote["iexRealtimePrice"]
        change = price - quote["previousClose"]
    else:
        price = quote["latestPrice"]
        change = quote["change"]

    def maybe(name, key, fmt=human_price):
        if quote.get(key):
            return " | {0}: {1}".format(name, fmt(float(quote[key])))
        return ""

    response = {
        "name": quote["companyName"],
        "change": change,
        "percent_change": 100 * change / (price - change),
        "symbol": quote["symbol"],
        "price": price,
        "color": "05" if change < 0 else "03",
        "high": quote["high"],
        "low": quote["low"],
        "average_volume": maybe("Volume", "latestVolume"),
        "market_cap": maybe("MCAP", "marketCap"),
        "pe_ratio": maybe("P/E", "peRatio", fmt="{:.2f}".format),
    }

    return (
        "{name} ({symbol}) ${price:,.2f} \x03{color}{change:,.2f} ({percent_change:,.2f}%)\x03"
        + (
            " | Day Range: ${low:,.2f} - ${high:,.2f}"
            if response["high"] and response["low"]
            else ""
        )
        + "{pe_ratio}{average_volume}{market_cap}"
    ).format(**response)

if __name__ == "__main__":
    import os, sys

    for arg in sys.argv[1:]:
        print(stock(arg, api_key=os.getenv("KEY")))


text:
"""
URI Normalization function:
 * Always provide the URI scheme in lowercase characters.
 * Always provide the host, if any, in lowercase characters.
 * Only perform percent-encoding where it is essential.
 * Always use uppercase A-through-F characters when percent-encoding.
 * Prevent dot-segments appearing in non-relative URI paths.
 * For schemes that define a default authority, use an empty authority if the
   default is desired.
 * For schemes that define an empty path to be equivalent to a path of "/",
   use "/".
 * For schemes that define a port, use an empty port if the default is desired
 * All portions of the URI must be utf-8 encoded NFC from Unicode strings

implements:
  http://gbiv.com/protocols/uri/rev-2002/rfc2396bis.html#canonical-form
  http://www.intertwingly.net/wiki/pie/PaceCanonicalIds

inspired by:
  Tony J. Ibbs,    http://starship.python.net/crew/tibs/python/tji_url.py
  Mark Nottingham, http://www.mnot.net/python/urlnorm.py
"""

from builtins import str
from builtins import object

__license__ = "Python"

from future.builtins import str

import re
import unicodedata
import urllib.parse
from urllib.parse import quote, unquote

default_port = {
    "http": 80,
}

class Normalizer(object):
    def __init__(self, regex, normalize_func):
        self.regex = regex
        self.normalize = normalize_func

normalizers = (
    Normalizer(
        re.compile(
            r"(?:https?://)?(?:[a-zA-Z0-9\-]+\.)?(?:amazon|amzn){1}\.(?P<tld>[a-zA-Z\.]{2,})\/(gp/(?:product|offer-listing|customer-media/product-gallery)/|exec/obidos/tg/detail/-/|o/ASIN/|dp/|(?:[A-Za-z0-9\-]+)/dp/)?(?P<ASIN>[0-9A-Za-z]{10})"
        ),
        lambda m: r"http://amazon.%s/dp/%s" % (m.group("tld"), m.group("ASIN")),
    ),
    Normalizer(
        re.compile(r".*waffleimages\.com.*/([0-9a-fA-F]{40})"),
        lambda m: r"http://img.waffleimages.com/%s" % m.group(1),
    ),
    Normalizer(
        re.compile(
            r"(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)([-_a-z0-9]+)"
        ),
        lambda m: r"http://youtube.com/watch?v=%s" % m.group(1),
    ),
)

def normalize(url):
    """Normalize a URL."""

    scheme, auth, path, query, fragment = urllib.parse.urlsplit(str(url.strip()))
    userinfo, host, port = re.search("([^@]*@)?([^:]*):?(.*)", auth).groups()

    # Always provide the URI scheme in lowercase characters.
    scheme = scheme.lower()

    # Always provide the host, if any, in lowercase characters.
    host = host.lower()
    if host and host[-1] == ".":
        host = host[:-1]
    if host and host.startswith("www."):
        if not scheme:
            scheme = "http"
        host = host[4:]
    elif path and path.startswith("www."):
        if not scheme:
            scheme = "http"
        path = path[4:]

    # Only perform percent-encoding where it is essential.
    # Always use uppercase A-through-F characters when percent-encoding.
    # All portions of the URI must be utf-8 encoded NFC from Unicode strings
    def clean(string):
        string = str(unquote(string))
        return unicodedata.normalize("NFC", string).encode("utf-8")

    path = quote(clean(path), "~:/?#[]@!$&'()*+,;=")
    fragment = quote(clean(fragment), "~")

    # note care must be taken to only encode & and = characters as values
    query = "&".join(
        [
            "=".join([quote(clean(t), "~:/?#[]@!$'()*+,;=") for t in q.split("=", 1)])
            for q in query.split("&")
        ]
    )

    # Prevent dot-segments appearing in non-relative URI paths.
    if scheme in ["", "http", "https", "ftp", "file"]:
        output = []
        for input in path.split("/"):
            if input == "":
                if not output:
                    output.append(input)
            elif input == ".":
                pass
            elif input == "..":
                if len(output) > 1:
                    output.pop()
            else:
                output.append(input)
        if input in ["", ".", ".."]:
            output.append("")
        path = "/".join(output)

    # For schemes that define a default authority, use an empty authority if
    # the default is desired.
    if userinfo in ["@", ":@"]:
        userinfo = ""

    # For schemes that define an empty path to be equivalent to a path of "/",
    # use "/".
    if path == "" and scheme in ["http", "https", "ftp", "file"]:
        path = "/"

    # For schemes that define a port, use an empty port if the default is
    # desired
    if port and scheme in list(default_port.keys()):
        if port.isdigit():
            port = str(int(port))
            if int(port) == default_port[scheme]:
                port = ""

    # Put it all back together again
    auth = (userinfo or "") + host
    if port:
        auth += ":" + port
    if url.endswith("#") and query == "" and fragment == "":
        path += "#"
    normal_url = urllib.parse.urlunsplit((scheme, auth, path, query, fragment)).replace(
        "http:///", "http://"
    )
    for norm in normalizers:
        m = norm.regex.match(normal_url)
        if m:
            return norm.normalize(m)
    return normal_url


text:
import inspect
import re

def _hook_add(func, add, name=""):
    if not hasattr(func, "_hook"):
        func._hook = []
    func._hook.append(add)

    if not hasattr(func, "_filename"):
        func._filename = func.__code__.co_filename

    if not hasattr(func, "_args"):
        argspec = inspect.getargspec(func)
        if name:
            n_args = len(argspec.args)
            if argspec.defaults:
                n_args -= len(argspec.defaults)
            if argspec.keywords:
                n_args -= 1
            if argspec.varargs:
                n_args -= 1
            if n_args != 1:
                err = "%ss must take 1 non-keyword argument (%s)" % (
                    name,
                    func.__name__,
                )
                raise ValueError(err)

        args = []
        if argspec.defaults:
            end = bool(argspec.keywords) + bool(argspec.varargs)
            args.extend(argspec.args[-len(argspec.defaults) : end if end else None])
        if argspec.keywords:
            args.append(0)  # means kwargs present
        func._args = args

    if not hasattr(func, "_thread"):  # does function run in its own thread?
        func._thread = False

def sieve(func):
    if func.__code__.co_argcount != 5:
        raise ValueError("sieves must take 5 arguments: (bot, input, func, type, args)")
    _hook_add(func, ["sieve", (func,)])
    return func

def command(arg=None, **kwargs):
    args = {}

    def command_wrapper(func):
        args.setdefault("name", func.__name__)
        _hook_add(func, ["command", (func, args)], "command")
        return func

    if kwargs or not inspect.isfunction(arg):
        if arg is not None:
            args["name"] = arg
        args.update(kwargs)
        return command_wrapper
    else:
        return command_wrapper(arg)

def event(arg=None, **kwargs):
    args = kwargs

    def event_wrapper(func):
        args["name"] = func.__name__
        args.setdefault("events", ["*"])
        _hook_add(func, ["event", (func, args)], "event")
        return func

    if inspect.isfunction(arg):
        return event_wrapper(arg, kwargs)
    else:
        if arg is not None:
            args["events"] = arg.split()
        return event_wrapper

def singlethread(func):
    func._thread = True
    return func

def api_key(*keys):
    def annotate(func):
        func._apikeys = keys
        return func

    return annotate

def regex(regex, flags=0, **kwargs):
    args = kwargs

    def regex_wrapper(func):
        args["name"] = func.__name__
        args["regex"] = regex
        args["re"] = re.compile(regex, flags)
        _hook_add(func, ["regex", (func, args)], "regex")
        return func

    if inspect.isfunction(regex):
        raise ValueError("regex decorators require a regex to match against")
    else:
        return regex_wrapper


text:
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  1. Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
#  3. Neither the name of Django nor the names of its contributors may be used
#     to endorse or promote products derived from this software without
#     specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import datetime

def timesince(d, now=None):
    """
    Takes two datetime objects and returns the time between d and now
    as a nicely formatted string, e.g. "10 minutes".  If d occurs after now,
    then "0 minutes" is returned.

    Units used are years, months, weeks, days, hours, and minutes.
    Seconds and microseconds are ignored.  Up to two adjacent units will be
    displayed.  For example, "2 weeks, 3 days" and "1 year, 3 months" are
    possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.

    Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
    """
    chunks = (
        (60 * 60 * 24 * 365, ("year", "years")),
        (60 * 60 * 24 * 30, ("month", "months")),
        (60 * 60 * 24 * 7, ("week", "weeks")),
        (60 * 60 * 24, ("day", "days")),
        (60 * 60, ("hour", "hours")),
        (60, ("minute", "minutes")),
    )

    # Convert int or float (unix epoch) to datetime.datetime for comparison
    if isinstance(d, int) or isinstance(d, float):
        d = datetime.datetime.fromtimestamp(d)

    # Convert datetime.date to datetime.datetime for comparison.
    if not isinstance(d, datetime.datetime):
        d = datetime.datetime(d.year, d.month, d.day)
    if now and not isinstance(now, datetime.datetime):
        now = datetime.datetime(now.year, now.month, now.day)

    if not now:
        now = datetime.datetime.now()

    # ignore microsecond part of 'd' since we removed it from 'now'
    delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
    since = delta.days * 24 * 60 * 60 + delta.seconds
    if since <= 0:
        # d is in the future compared to now, stop processing.
        return "0 " + "minutes"
    for i, (seconds, name) in enumerate(chunks):
        count = since // seconds
        if count != 0:
            break

    if count == 1:
        s = "%(number)d %(type)s" % {"number": count, "type": name[0]}
    else:
        s = "%(number)d %(type)s" % {"number": count, "type": name[1]}

    if i + 1 < len(chunks):
        # Now get the second item
        seconds2, name2 = chunks[i + 1]
        count2 = (since - (seconds * count)) // seconds2
        if count2 != 0:
            if count2 == 1:
                s += ", %d %s" % (count2, name2[0])
            else:
                s += ", %d %s" % (count2, name2[1])
    return s

def timeuntil(d, now=None):
    """
    Like timesince, but returns a string measuring the time until
    the given time.
    """
    if not now:
        now = datetime.datetime.now()
    return timesince(now, d)


text:
from future.standard_library import hooks

from lxml import etree, html

import binascii
import collections
import hmac
import json
import random
import time

from hashlib import sha1

from builtins import str
from builtins import range

try:
    from http.cookiejar import CookieJar
except:
    from future.backports.http.cookiejar import CookieJar

with hooks():
    import urllib.request, urllib.parse, urllib.error

    from urllib.parse import (
        quote,
        unquote,
        urlencode,
        urlparse,
        parse_qsl,
        quote_plus as _quote_plus,
    )
    from urllib.error import HTTPError, URLError

ua_skybot = "Skybot/1.0 https://github.com/rmmh/skybot"
ua_firefox = (
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) "
    "Gecko/20070725 Firefox/2.0.0.6"
)
ua_internetexplorer = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"

def get_cookie_jar():
    if not hasattr(get_cookie_jar, "memo"):
        get_cookie_jar.memo = CookieJar()

    return get_cookie_jar.memo

def clear_expired_cookies():
    get_cookie_jar().clear_expired_cookies()

def get(*args, **kwargs):
    return open(*args, **kwargs).read().decode("utf-8")

def get_html(*args, **kwargs):
    return html.fromstring(open(*args, **kwargs).read())

def get_xml(*args, **kwargs):
    return etree.fromstring(open(*args, **kwargs).read())

def get_json(*args, **kwargs):
    return json.loads(open(*args, **kwargs).read())

def open(
    url,
    query_params=None,
    post_data=None,
    json_data=None,
    get_method=None,
    cookies=False,
    oauth=False,
    oauth_keys=None,
    headers=None,
    **kwargs
):
    if query_params is None:
        query_params = {}

    query_params.update(kwargs)

    url = prepare_url(url, query_params)

    if post_data and isinstance(post_data, collections.Mapping):
        post_data = urllib.parse.urlencode(post_data)
        post_data = post_data.encode("UTF-8")

    if json_data and isinstance(json_data, dict):
        post_data = json.dumps(json_data).encode("utf-8")

    request = urllib.request.Request(url, post_data)

    if json_data:
        request.add_header("Content-Type", "application/json")

    if get_method is not None:
        request.get_method = lambda: get_method

    if headers is not None:
        for header_key, header_value in headers.items():
            request.add_header(header_key, header_value)

    if "User-Agent" not in request.headers:
        request.add_header("User-Agent", ua_skybot)

    if oauth:
        nonce = oauth_nonce()
        timestamp = oauth_timestamp()
        api_url, req_data = url.split("?")
        unsigned_request = oauth_unsigned_request(
            nonce, timestamp, req_data, oauth_keys["consumer"], oauth_keys["access"]
        )

        signature = oauth_sign_request(
            "GET",
            api_url,
            req_data,
            unsigned_request,
            oauth_keys["consumer_secret"],
            oauth_keys["access_secret"],
        )

        header = oauth_build_header(
            nonce, signature, timestamp, oauth_keys["consumer"], oauth_keys["access"]
        )
        request.add_header("Authorization", header)

    if cookies:
        opener = urllib.request.build_opener(
            urllib.request.HTTPCookieProcessor(get_cookie_jar())
        )
    else:
        opener = urllib.request.build_opener()

    return opener.open(request)

def prepare_url(url, queries):
    if queries:
        scheme, netloc, path, query, fragment = urllib.parse.urlsplit(str(url))

        query = dict(urllib.parse.parse_qsl(query))
        query.update(queries)
        query = urllib.parse.urlencode(
            dict((to_utf8(key), to_utf8(value)) for key, value in query.items())
        )

        url = urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))

    return url

def to_utf8(s):
    if isinstance(s, str):
        return s.encode("utf8", "ignore")
    else:
        return str(s)

def quote_plus(s):
    return _quote_plus(to_utf8(s))

def oauth_nonce():
    return "".join([str(random.randint(0, 9)) for i in range(8)])

def oauth_timestamp():
    return str(int(time.time()))

def oauth_unsigned_request(nonce, timestamp, req, consumer, token):
    d = {
        "oauth_consumer_key": consumer,
        "oauth_nonce": nonce,
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": timestamp,
        "oauth_token": token,
        "oauth_version": "1.0",
    }

    d.update(urllib.parse.parse_qsl(req))

    request_items = d.items()

    # TODO: Remove this when Python 2 is no longer supported.
    # some of the fields are actual string and others are
    # a wrapper of str for the python 3 migration.
    # Convert them all so that they sort correctly.
    request_items = [(str(k), str(v)) for k, v in request_items]

    return quote(urllib.parse.urlencode(sorted(request_items, key=lambda key: key[0])))

def oauth_build_header(nonce, signature, timestamp, consumer, token):
    d = {
        "oauth_consumer_key": consumer,
        "oauth_nonce": nonce,
        "oauth_signature": signature,
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": timestamp,
        "oauth_token": token,
        "oauth_version": "1.0",
    }

    header = "OAuth "

    for x in sorted(d, key=lambda key: key[0]):
        header += x + '="' + d[x] + '", '

    return header[:-1]

def oauth_sign_request(
    method, url, params, unsigned_request, consumer_secret, token_secret
):
    key = consumer_secret + "&" + token_secret
    key = key.encode("utf-8", "replace")

    base = method + "&" + quote(url, "") + "&" + unsigned_request
    base = base.encode("utf-8", "replace")

    hash = hmac.new(key, base, sha1)

    signature = quote(binascii.b2a_base64(hash.digest())[:-1])

    return signature

def unescape(s):
    if not s.strip():
        return s
    return html.fromstring(s).text_content()


text:
from os import path
import json
import inspect

def get_fixture_file(testcase, file):
    filename = inspect.getfile(testcase.__class__)

    test_name, _ = path.splitext(filename)
    dir_path = path.dirname(path.realpath(filename))

    with open(path.join(dir_path, test_name, file)) as f:
        return f.read()

def get_fixture_file_data(testcase, file):
    return json.loads(get_fixture_file(testcase, file))

def execute_skybot_regex(command_method, inp, **kwargs):
    for type, (func, func_args) in command_method._hook:
        if not type == "regex" or "re" not in func_args:
            continue

        search_result = func_args["re"].search(inp)

        if search_result:
            return command_method(search_result, **kwargs)

    return None


text:
from os import path
from unittest import TestSuite, TestLoader, TextTestRunner
import sys

if __name__ == "__main__":
    # Because the project is structured differently than
    # any tooling expects, we need to modify the python
    # path during runtime (or before) to get it to
    # properly import plugins and other code correctly.
    project_root_directory = path.dirname(path.dirname(__file__))

    sys.path.append(path.join(project_root_directory, "plugins"))
    sys.path.append(path.join(project_root_directory))

    discovered_tests = TestLoader().discover(path.dirname(__file__))
    run_result = TextTestRunner().run(discovered_tests)

    if not run_result.wasSuccessful():
        sys.exit(1)


text:
from unittest import TestCase
from mock import patch

from choose import choose

class TestChoose(TestCase):
    def test_choose_one_choice(self):
        expected = "the decision is up to you"
        actual = choose("foo")

        assert expected == actual

    def test_choose_same_thing(self):
        expected = "foo"
        actual = choose("foo, foo, foo")

        assert expected == actual

    def test_choose_two_choices(self):
        actual = choose("foo, bar")

        assert actual in ["foo", "bar"]

    def test_choose_choices_space(self):
        expected_values = ["foo", "bar"]
        actual = choose("foo bar")

        assert actual in expected_values

    def test_choose_strips_whitespace(self):
        expected_values = ["foo", "bar"]
        actual = choose("          foo             ," "           bar             ")

        assert actual in expected_values

    @patch("random.choice")
    def test_choose_end_comma_behavior(self, mock_random_choice):
        mock_random_choice.side_effect = lambda arr: arr[0]

        expected = "the decision is up to you"
        actual = choose("foo,")

        assert actual == expected

    @patch("random.choice")
    def test_choose_collapse_commas(self, mock_random_choice):
        # Should never be an empty string here
        mock_random_choice.side_effect = lambda arr: arr[1]

        expected = "bar"
        actual = choose("foo,,bar")

        assert actual == expected


text:
from unittest import TestCase
from bf import bf

class TestBF(TestCase):
    def test_hello(self):
        expected = "Hello world!"
        actual = bf(
            "--[>--->->->++>-<<<<<-------]>--.>---------.>--..+++.>---"
            "-.>+++++++++.<<.+++.------.<-.>>+."
        )

        assert expected == actual

    def test_unbalanced(self):
        expected = "unbalanced brackets"

        actual_a = bf("[[++]]]")
        actual_b = bf("[[[++]]")

        assert expected == actual_a
        assert expected == actual_b

    def test_comment(self):
        expected = "*"
        actual = bf("[this is a comment!]++++++[>+++++++<-]>.")

        assert expected == actual

    def test_unprintable(self):
        expected = "no printable output"
        actual = bf("+.")

        assert expected == actual

    def test_empty(self):
        expected = "no output"
        actual = bf("+++[-]")

        assert expected == actual

    def test_exceeded(self):
        expected = "no output [exceeded 1000 iterations]"
        actual = bf("+[>,[-]<]", 1000)

        assert expected == actual

    def test_inf_mem(self):
        expected = "no output [exceeded 1000 iterations]"
        actual = bf("+[>[.-]+]", 1000, buffer_size=10)

        assert expected == actual

    def test_left_wrap(self):
        # eventually, wrap around and hit ourselves
        expected = "aaaaaaa [exceeded 2000 iterations]"
        actual = bf("+[<[-" + "+" * ord("a") + ".[-]]+]", 2000, buffer_size=5)

        assert expected == actual

    def test_too_much_output(self):
        expected = "a" * 430
        actual = bf("+" * ord("a") + "[.]")

        assert expected == actual


text:
from unittest import TestCase

from mock import patch

from helpers import get_fixture_file_data, execute_skybot_regex
from hackernews import hackernews

class TestHackernews(TestCase):
    @patch("util.http.get_json")
    def test_story(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "9943431.json")

        expected = (
            u"Can Desalination Counter the Drought? by cwal37 "
            u"with 51 points and 94 comments "
            u"(http://www.newyorker.com/tech/elements/can-"
            u"desalination-counter-the-drought)"
        )

        url = "https://news.ycombinator.com/item?id=9943431"
        actual = execute_skybot_regex(hackernews, url)

        assert expected == actual

    @patch("util.http.get_json")
    def test_comment(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "9943987.json")

        expected = (
            u'"Yes, they must have meant kilowatt hours. Was '
            u'there no editor?" -- oaktowner'
        )

        url = "https://news.ycombinator.com/item?id=9943987"
        actual = execute_skybot_regex(hackernews, url)

        assert expected == actual

    @patch("util.http.get_json")
    def test_comment_encoding(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "9943897.json")

        expected = (
            u'"> All told, it takes about 3460 kilowatts per '
            u"acre-foot to pump water from Northern California "
            u"to San Diego; Carlsbad will use about thirty per "
            u"cent more energy, five thousand kilowatts per "
            u"acre-foot, to desalinate ocean water and deliver "
            u"it to households, according to Poseidon\u2019s "
            u"report to the Department of Water Resources // "
            u"These units are abominations.  Couldn't just "
            u"say 2.8 Watts per liter vs 4.0 Watts per liter?  "
            u"Or even 10.6 and 15.3 Watts per gallon?  I'm "
            u"not a metric purist, but the only advantage to "
            u"using imperial units is that they are more "
            u"familiar to the average American, but when does "
            u'the average person deal with acre-feet?" '
            u"-- alwaysdoit"
        )

        url = "https://news.ycombinator.com/item?id=9943897"
        actual = execute_skybot_regex(hackernews, url)

        assert expected == actual


text:
from collections import namedtuple
from unittest import TestCase
from mock import Mock, patch, call

from helpers import execute_skybot_regex
from crowdcontrol import crowdcontrol

class TestCrowdcontrol(TestCase):
    def call_crowd_control(self, input, called=None, rules=None):
        mock_names = ["kick", "ban", "unban", "reply"]

        mocks = {}

        for name in mock_names:
            mocks[name] = Mock(name=name)

        config = {"crowdcontrol": rules} if rules else {}
        bot = namedtuple("Bot", "config")(config=config)

        execute_skybot_regex(crowdcontrol, input, bot=bot, **mocks)

        return mocks

    def test_no_rules(self):
        mocks = self.call_crowd_control("Hello world!")

        for m in mocks.values():
            m.assert_not_called()

    def test_no_matches(self):
        mocks = self.call_crowd_control("Hello world!", rules=[{"re": "No match"}])

        for m in mocks.values():
            m.assert_not_called()

    def test_match_no_action(self):
        mocks = self.call_crowd_control("Hello world!", rules=[{"re": "Hello"}])

        for m in mocks.values():
            m.assert_not_called()

    def test_match_only_msg(self):
        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "msg": "Hello!"}]
        )

        for n, m in mocks.items():
            if n == "reply":
                m.assert_called_once_with("Hello!")
            else:
                m.assert_not_called()

    def test_match_ban_forever_no_kick(self):
        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "ban_length": -1}]
        )

        for n, m in mocks.items():
            if n == "ban":
                m.assert_called_once()
            else:
                m.assert_not_called()

    def test_match_kick_no_ban(self):
        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "kick": 1}]
        )

        for n, m in mocks.items():
            if n == "kick":
                m.assert_called_once()
            else:
                m.assert_not_called()

    def test_match_kick_with_msg(self):
        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "kick": 1, "msg": "Hello!"}]
        )

        for n, m in mocks.items():
            if n == "kick":
                m.assert_called_once_with(reason="Hello!")
            else:
                m.assert_not_called()

    def test_match_kick_ban_forever(self):
        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "kick": 1, "ban_length": -1}]
        )

        for n, m in mocks.items():
            if n == "kick" or n == "ban":
                m.assert_called_once()
            else:
                m.assert_not_called()

    @patch("time.sleep")
    def test_match_ban_only_time_limit(self, mock_time_sleep):

        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "ban_length": 5}]
        )

        mock_time_sleep.assert_called_once_with(5)

        for n, m in mocks.items():
            if n == "ban" or n == "unban":
                m.assert_called_once()
            else:
                m.assert_not_called()

    @patch("time.sleep")
    def test_match_kick_ban_time_limit(self, mock_time_sleep):

        mocks = self.call_crowd_control(
            "Hello world!", rules=[{"re": "Hello", "kick": 1, "ban_length": 5}]
        )

        mock_time_sleep.assert_called_once_with(5)

        for n, m in mocks.items():
            if n == "kick" or n == "ban" or n == "unban":
                m.assert_called_once()
            else:
                m.assert_not_called()

    def test_match_multiple_rules_in_order(self):
        mocks = self.call_crowd_control(
            "Hello world!",
            rules=[
                {"re": "Hello", "msg": "1"},
                {"re": "Fancy", "msg": "2"},
                {"re": "[wW]orld", "msg": "3"},
            ],
        )

        for n, m in mocks.items():
            if n == "reply":
                m.assert_has_calls([call("1"), call("3")])
            else:
                m.assert_not_called()


text:
from unittest import skip, TestCase
from mock import patch

from dice import dice

class TestDice(TestCase):
    @patch("random.randint")
    def test_one_d20(self, mock_random_randint):
        mock_random_randint.return_value = 5

        expected = "5 (d20=5)"
        actual = dice("d20")

        assert expected == actual

    @skip("skip until https://github.com/rmmh/skybot/pull/187 is merged")
    @patch("random.randint")
    def test_complex_roll(self, mock_random_randint):
        mock_random_randint.side_effect = iter([1, 2, 3])

        expected = u"4 (2d20-d5+4=1, 2, -3)"
        actual = dice("2d20-d5+4")

        assert expected == actual

    def test_constant_roll(self):
        # This fails so it is "none"
        actual = dice("1234")

        assert actual is None

    @patch("random.randint")
    def test_fudge_dice(self, mock_random_randint):
        mock_random_randint.side_effect = iter([-1, 0, 1, 0, -1])

        expected = "-1 (5dF=\x034-\x0f, 0, \x033+\x0f, 0, \x034-\x0f)"
        actual = dice("5dF")

        assert expected == actual

    @patch("random.randint")
    def test_fudge_dice(self, mock_random_randint):
        mock_random_randint.side_effect = iter([-1, 0, 1, 0, -1])

        expected = "-1 (5dF=\x034-\x0f, 0, \x033+\x0f, 0, \x034-\x0f)"
        actual = dice("5dF")

        assert expected == actual


text:
from unittest import TestCase
from mock import Mock, patch

from helpers import get_fixture_file_data
from crypto import crypto

class TestCrypto(TestCase):
    @patch("util.http.get_json")
    def test_crypto_btc_to_default(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(
            self, "crypto_btc_to_default.json"
        )

        expected = (
            u"USD/\u0243: \x0307$ 6,084.30\x0f - High: "
            u"\x0307$ 6,178.90\x0f - Low: \x0307$ 6,014.26"
            u"\x0f - Volume: \u0243 21,428.7 "
            u"($ 131,067,493.3) - Total Supply: "
            u"\u0243 17,202,675.0 - MktCap: $ 104.67 B"
        )

        say_mock = Mock()
        crypto("btc", say=say_mock)

        say_mock.assert_called_once_with(expected)

    @patch("util.http.get_json")
    def test_crypto_eth_to_default(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(
            self, "crypto_eth_to_default.json"
        )

        expected = (
            u"USD/\u039e: \x0307$ 315.91\x0f - High: "
            u"\x0307$ 332.41\x0f - Low: \x0307$ 312.35\x0f "
            u"- Volume: \u039e 163,011.9 ($ 52,513,531.9) - "
            u"Total Supply: \u039e 101,251,550.4 - "
            u"MktCap: $ 31.99 B"
        )

        say_mock = Mock()
        crypto("eth", say_mock)

        say_mock.assert_called_once_with(expected)


text:
from unittest import TestCase
from mock import patch

from cdecl import cdecl

class TestCDecl(TestCase):
    @patch("util.http.get")
    def test_cdecl(self, mock_http_get):
        mock_http_get.side_effect = [
            'var QUERY_ENDPOINT = "http://foo.bar"',
            '"declare x as array 3 of pointer to function returning pointer to array 5 of char"',
        ]

        expected = '"declare x as array 3 of pointer to function returning pointer to array 5 of char"'
        actual = cdecl("char (*(*x())[5])()")

        assert expected == actual


text:
from unittest import TestCase

from mock import patch

from helpers import get_fixture_file_data, execute_skybot_regex
from twitter import show_tweet, twitter

FAKE_API_KEY = {
    "consumer": "AAAAAAAAAAAAAAAAAAAAAAAAA",
    "consumer_secret": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "access": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "access_secret": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
}

class TestTwitter(TestCase):
    @patch("util.http.get_json")
    def test_tweet_regex(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(
            self, "1014260007771295745.json"
        )

        expected = (
            u"2018-07-03 21:30:04 \x02jk_rowling\x02: "
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"hahahaha "
            u"*draws breath* "
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"hahahahahahahahahahahahahahahahahahahahaha"
            u"haha https://t.co/gbionAPK9Z"
        )

        url = "https://twitter.com/jk_rowling/status/1014260007771295745"
        actual = execute_skybot_regex(show_tweet, url, api_key=FAKE_API_KEY)

        assert expected == actual

    @patch("util.http.get_json")
    def test_twitter_username_no_tweet_number(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(
            self, "user_loneblockbuster.json"
        )

        expected = (
            u"2018-08-16 17:38:52 \x02loneblockbuster\x02: "
            u"We had the motto \"When you're here you're "
            u'family" before Olive Garden but like '
            u"everything else it was taken away "
            u"from us."
        )

        actual = twitter("loneblockbuster", api_key=FAKE_API_KEY)

        assert expected == actual

    @patch("util.http.get_json")
    def test_twitter_username_with_tweet_number(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(
            self, "user_loneblockbuster.json"
        )

        expected = (
            u"2018-07-24 19:30:59 \x02loneblockbuster\x02: "
            u"We never would have planted the ferns out "
            u"front if we knew we'd get so many death threats."
        )

        actual = twitter("loneblockbuster 10", api_key=FAKE_API_KEY)

        assert expected == actual

    @patch("random.randint")
    @patch("util.http.get_json")
    def test_twitter_hashtag_no_tweet_number(self, mock_http_get, mock_random_randint):
        mock_http_get.return_value = get_fixture_file_data(self, "hashtag_nyc.json")

        # This plugin chooses a random value.
        # I chose this value randomly by rolling a D20.
        mock_random_randint.return_value = 6

        expected = (
            u"2018-08-17 20:19:56 \x02The_Carl_John\x02: "
            u"RT @ItVisn How many records can your "
            u"company afford to lose? https://t.co/iJzFYtJmCh "
            u"Be proactive and Protect Your Business "
            u"#CyberSecurity #DataProtection "
            u"#DataBreaches #SmallBusiness #Tech "
            u"#Data #NYC #Technology #DarkWeb #Ransomware "
            u"#Malware #Phishing #Business "
            u"https://t.co/xAJVRhjOww"
        )

        actual = twitter("#NYC", api_key=FAKE_API_KEY)

        assert expected == actual

    @patch("util.http.get_json")
    def test_twitter_hashtag_with_tweet_number(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "hashtag_nyc.json")

        expected = (
            u"2018-08-17 20:19:32 \x02Kugey\x02: "
            u"I know for sure that life is beautiful "
            u"around the world... \u2022 \u2022 Here "
            u"is yet another iconic piece of the "
            u"NYC skyline... \u2022 \u2022 #nyc "
            u"#photography #streetphotography "
            u"#urbanphotography\u2026 "
            u"https://t.co/bq9i0FZN89"
        )

        actual = twitter("#NYC 10", api_key=FAKE_API_KEY)

        assert expected == actual


text:
from unittest import TestCase
from mock import patch, Mock

from helpers import get_fixture_file_data
from bitcoin import bitcoin, ethereum

class TestBitcoin(TestCase):
    @patch("util.http.get_json")
    def test_bitcoin(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "bitcoin.json")

        say_mock = Mock()

        bitcoin("", say=say_mock)

        expected = (
            "USD/BTC: \x0307$6,390.67\x0f - High: \x0307$6,627."
            "00\x0f - Low: \x0307$6,295.73\x0f - Volume: "
            "10,329.67 BTC"
        )
        say_mock.assert_called_once_with(expected)

    @patch("util.http.get_json")
    def test_ethereum(self, mock_http_get):
        mock_http_get.return_value = get_fixture_file_data(self, "ethereum.json")

        say_mock = Mock()

        ethereum("", say=say_mock)

        expected = (
            "USD/ETH: \x0307$355.02\x0f - High: \x0307$370.43"
            "\x0f - Low: \x0307$350.00\x0f - Volume: "
            "16,408.97 ETH"
        )

        say_mock.assert_called_once_with(expected)


text:
# Contribution guidlines

Contributions are very welcome! If you've got a theme, plugin, tool, CSS snippet or anything exciting related to Obsidian that you want included in this list, you're welcome to submit a PR.

All PRs are manually reviewed, so don't fret if your submission is not merged at once. To aid the maintainers and have your PR approved more quickly, go over this checklist:

- Abide by the already established style of the list.

  - If you addition is part of a table, make sure it is correctly formatted.
  - If the section has a table of contents, update that as well.

- Make sure your addition is part of the correct section/category.
- Don't sneak in line. Append your addition to the **end** of the correct section. The ordering is handled by the maintainers. If you intentionally place yourself at the top, we will move you down to the bottom.
- If your addition is a CSS snippet, make sure that the CSS file is named fittingly, and that you include an example image in the README.

As long as you follow these guidelines, you will probably get merged. If anything is unclear, just submit a draft PR, and we can discuss how to progress from there :smile:

repo_name: kmaasrud/awesome-obsidian

repo_language: CSS

mime_type: text/plain