<commit_before># -*- coding: utf-8 -*-
# File: coco.py

import numpy as np
import os
import tqdm
import json

from tensorpack.utils import logger
from tensorpack.utils.timer import timed_operation

from config import config as cfg

__all__ = ['COCODetection', 'DetectionDataset']

class COCODetection(object):
    # handle the weird (but standard) split of train and val
    _INSTANCE_TO_BASEDIR = {
        'valminusminival2014': 'val2014',
        'minival2014': 'val2014',
    }

    COCO_id_to_category_id = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 13: 12, 14: 13, 15: 14, 16: 15, 17: 16, 18: 17, 19: 18, 20: 19, 21: 20, 22: 21, 23: 22, 24: 23, 25: 24, 27: 25, 28: 26, 31: 27, 32: 28, 33: 29, 34: 30, 35: 31, 36: 32, 37: 33, 38: 34, 39: 35, 40: 36, 41: 37, 42: 38, 43: 39, 44: 40, 46: 41, 47: 42, 48: 43, 49: 44, 50: 45, 51: 46, 52: 47, 53: 48, 54: 49, 55: 50, 56: 51, 57: 52, 58: 53, 59: 54, 60: 55, 61: 56, 62: 57, 63: 58, 64: 59, 65: 60, 67: 61, 70: 62, 72: 63, 73: 64, 74: 65, 75: 66, 76: 67, 77: 68, 78: 69, 79: 70, 80: 71, 81: 72, 82: 73, 84: 74, 85: 75, 86: 76, 87: 77, 88: 78, 89: 79, 90: 80}  # noqa
    """
    Mapping from the incontinuous COCO category id to an id in [1, #category]
    For your own dataset, this should usually be an identity mapping.
    """

    # 80 names for COCO
    class_names = [
        "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]  # noqa

    def __init__(self, basedir, name):
        self.name = name
        self._imgdir = os.path.realpath(os.path.join(
            basedir, self._INSTANCE_TO_BASEDIR.get(name, name)))
        assert os.path.isdir(self._imgdir), self._imgdir
        annotation_file = os.path.join(
            basedir, 'annotations/instances_{}.json'.format(name))
        assert os.path.isfile(annotation_file), annotation_file

        from pycocotools.coco import COCO
        self.coco = COCO(annotation_file)
        logger.info("Instances loaded from {}.".format(annotation_file))

    # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
    def print_coco_metrics(self, json_file):
        """
        Args:
            json_file (str): path to the results json file in coco format
        Returns:
            dict: the evaluation metrics
        """
        from pycocotools.cocoeval import COCOeval
        ret = {}
        cocoDt = self.coco.loadRes(json_file)
        cocoEval = COCOeval(self.coco, cocoDt, 'bbox')
        cocoEval.evaluate()
        cocoEval.accumulate()
        cocoEval.summarize()
        fields = ['IoU=0.5:0.95', 'IoU=0.5', 'IoU=0.75', 'small', 'medium', 'large']
        for k in range(6):
            ret['mAP(bbox)/' + fields[k]] = cocoEval.stats[k]

        json_obj = json.load(open(json_file))
        if len(json_obj) > 0 and 'segmentation' in json_obj[0]:
            cocoEval = COCOeval(self.coco, cocoDt, 'segm')
            cocoEval.evaluate()
            cocoEval.accumulate()
            cocoEval.summarize()
            for k in range(6):
                ret['mAP(segm)/' + fields[k]] = cocoEval.stats[k]
        return ret

    def load(self, add_gt=True, add_mask=False):
        """
        Args:
            add_gt: whether to add ground truth bounding box annotations to the dicts
            add_mask: whether to also add ground truth mask

        Returns:
            a list of dict, each has keys including:
                'height', 'width', 'id', 'file_name',
                and (if add_gt is True) 'boxes', 'class', 'is_crowd', and optionally
                'segmentation'.
        """
        if add_mask:
            assert add_gt
        with timed_operation('Load Groundtruth Boxes for {}'.format(self.name)):
            img_ids = self.coco.getImgIds()
            img_ids.sort()
            # list of dict, each has keys: height,width,id,file_name
            imgs = self.coco.loadImgs(img_ids)

            for img in tqdm.tqdm(imgs):
                self._use_absolute_file_name(img)
                if add_gt:
                    self._add_detection_gt(img, add_mask)
            return imgs

    def _use_absolute_file_name(self, img):
        """
        Change relative filename to abosolute file name.
        """
        img['file_name'] = os.path.join(
            self._imgdir, img['file_name'])
        assert os.path.isfile(img['file_name']), img['file_name']

    def _add_detection_gt(self, img, add_mask):
        """
        Add 'boxes', 'class', 'is_crowd' of this image to the dict, used by detection.
        If add_mask is True, also add 'segmentation' in coco poly format.
        """
        # ann_ids = self.coco.getAnnIds(imgIds=img['id'])
        # objs = self.coco.loadAnns(ann_ids)
        objs = self.coco.imgToAnns[img['id']]  # equivalent but faster than the above two lines

        # clean-up boxes
        valid_objs = []
        width = img['width']
        height = img['height']
        for objid, obj in enumerate(objs):
            if obj.get('ignore', 0) == 1:
                continue
            x1, y1, w, h = obj['bbox']
            # bbox is originally in float
            # x1/y1 means upper-left corner and w/h means true w/h. This can be verified by segmentation pixels.
            # But we do make an assumption here that (0.0, 0.0) is upper-left corner of the first pixel

            x1 = np.clip(float(x1), 0, width)
            y1 = np.clip(float(y1), 0, height)
            w = np.clip(float(x1 + w), 0, width) - x1
            h = np.clip(float(y1 + h), 0, height) - y1
            # Require non-zero seg area and more than 1x1 box size
            if obj['area'] > 1 and w > 0 and h > 0 and w * h >= 4:
                obj['bbox'] = [x1, y1, x1 + w, y1 + h]
                valid_objs.append(obj)

                if add_mask:
                    segs = obj['segmentation']
                    if not isinstance(segs, list):
                        assert obj['iscrowd'] == 1
                        obj['segmentation'] = None
                    else:
                        valid_segs = [np.asarray(p).reshape(-1, 2).astype('float32') for p in segs if len(p) >= 6]
                        if len(valid_segs) == 0:
                            logger.error("Object {} in image {} has no valid polygons!".format(objid, img['file_name']))
                        elif len(valid_segs) < len(segs):
                            logger.warn("Object {} in image {} has invalid polygons!".format(objid, img['file_name']))

                        obj['segmentation'] = valid_segs

        # all geometrically-valid boxes are returned
        boxes = np.asarray([obj['bbox'] for obj in valid_objs], dtype='float32')  # (n, 4)
        cls = np.asarray([
            self.COCO_id_to_category_id[obj['category_id']]
            for obj in valid_objs], dtype='int32')  # (n,)
        is_crowd = np.asarray([obj['iscrowd'] for obj in valid_objs], dtype='int8')

        # add the keys
        img['boxes'] = boxes        # nx4
        img['class'] = cls          # n, always >0
        img['is_crowd'] = is_crowd  # n,
        if add_mask:
            # also required to be float32
            img['segmentation'] = [
                obj['segmentation'] for obj in valid_objs]

    @staticmethod
    def load_many(basedir, names, add_gt=True, add_mask=False):
        """
        Load and merges several instance files together.

        Returns the same format as :meth:`COCODetection.load`.
        """
        if not isinstance(names, (list, tuple)):
            names = [names]
        ret = []
        for n in names:
            coco = COCODetection(basedir, n)
            ret.extend(coco.load(add_gt, add_mask=add_mask))
        return ret

class DetectionDataset(object):
    """
    A singleton to load datasets, evaluate results, and provide metadata.

    To use your own dataset that's not in COCO format, rewrite all methods of this class.
    """
    def __init__(self):
        """
        This function is responsible for setting the dataset-specific
        attributes in both cfg and self.
        """
        self.num_category = cfg.DATA.NUM_CATEGORY = len(COCODetection.class_names)
        self.num_classes = self.num_category + 1
        self.class_names = cfg.DATA.CLASS_NAMES = ["BG"] + COCODetection.class_names

    def load_training_roidbs(self, names):
        """
        Args:
            names (list[str]): name of the training datasets, e.g.  ['train2014', 'valminusminival2014']

        Returns:
            roidbs (list[dict]):

        Produce "roidbs" as a list of dict, each dict corresponds to one image with k>=0 instances.
        and the following keys are expected for training:

        height, width: integer
        file_name: str, full path to the image
        boxes: numpy array of kx4 floats, each row is [x1, y1, x2, y2]
        category: numpy array of k integers, in the range of [1, #categories]
        is_crowd: k booleans. Use k False if you don't know what it means.
        segmentation: k lists of numpy arrays (one for each instance).
            Each list of numpy arrays corresponds to the mask for one instance.
            Each numpy array in the list is a polygon of shape Nx2,
            because one mask can be represented by N polygons.

            If your segmentation annotations are originally masks rather than polygons,
            either convert it, or the augmentation will need to be changed or skipped accordingly.

            Include this field only if training Mask R-CNN.
        """
        return COCODetection.load_many(
            cfg.DATA.BASEDIR, cfg.DATA.TRAIN, add_gt=True, add_mask=cfg.MODE_MASK)

    def load_inference_roidbs(self, name):
        """
        Args:
            name (str): name of one inference dataset, e.g. 'minival2014'

        Returns:
            roidbs (list[dict]):

            Each dict corresponds to one image to run inference on. The
            following keys in the dict are expected:

            file_name (str): full path to the image
            id (str): an id for the image. The inference results will be stored with this id.
        """
        return COCODetection.load_many(cfg.DATA.BASEDIR, name, add_gt=False)

    def eval_or_save_inference_results(self, results, dataset, output=None):
        """
        Args:
            results (list[dict]): the inference results as dicts.
                Each dict corresponds to one __instance__. It contains the following keys:

                image_id (str): the id that matches `load_inference_roidbs`.
                category_id (int): the category prediction, in range [1, #category]
                bbox (list[float]): x1, y1, x2, y2
                score (float):
                segmentation: the segmentation mask in COCO's rle format.

            dataset (str): the name of the dataset to evaluate.
            output (str): the output file to optionally save the results to.

        Returns:
            dict: the evaluation results.
        """
        continuous_id_to_COCO_id = {v: k for k, v in COCODetection.COCO_id_to_category_id.items()}
        for res in results:
            # convert to COCO's incontinuous category id
            res['category_id'] = continuous_id_to_COCO_id[res['category_id']]
            # COCO expects results in xywh format
            box = res['bbox']
            box[2] -= box[0]
            box[3] -= box[1]
            res['bbox'] = [round(float(x), 3) for x in box]

        assert output is not None, "COCO evaluation requires an output file!"
        with open(output, 'w') as f:
            json.dump(results, f)
        if len(output):
            # sometimes may crash if the results are empty?
            return COCODetection(cfg.DATA.BASEDIR, dataset).print_coco_metrics(output)
        else:
            return {}

    # code for singleton:
    _instance = None

    def __new__(cls):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls)
        return cls._instance

if __name__ == '__main__':
    c = COCODetection(cfg.DATA.BASEDIR, 'train2014')
    gt_boxes = c.load(add_gt=True, add_mask=True)
    print("#Images:", len(gt_boxes))
<commit_msg>fix typo (fix #1088)<commit_after># -*- coding: utf-8 -*-
# File: coco.py

import numpy as np
import os
import tqdm
import json

from tensorpack.utils import logger
from tensorpack.utils.timer import timed_operation

from config import config as cfg

__all__ = ['COCODetection', 'DetectionDataset']

class COCODetection(object):
    # handle the weird (but standard) split of train and val
    _INSTANCE_TO_BASEDIR = {
        'valminusminival2014': 'val2014',
        'minival2014': 'val2014',
    }

    COCO_id_to_category_id = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 13: 12, 14: 13, 15: 14, 16: 15, 17: 16, 18: 17, 19: 18, 20: 19, 21: 20, 22: 21, 23: 22, 24: 23, 25: 24, 27: 25, 28: 26, 31: 27, 32: 28, 33: 29, 34: 30, 35: 31, 36: 32, 37: 33, 38: 34, 39: 35, 40: 36, 41: 37, 42: 38, 43: 39, 44: 40, 46: 41, 47: 42, 48: 43, 49: 44, 50: 45, 51: 46, 52: 47, 53: 48, 54: 49, 55: 50, 56: 51, 57: 52, 58: 53, 59: 54, 60: 55, 61: 56, 62: 57, 63: 58, 64: 59, 65: 60, 67: 61, 70: 62, 72: 63, 73: 64, 74: 65, 75: 66, 76: 67, 77: 68, 78: 69, 79: 70, 80: 71, 81: 72, 82: 73, 84: 74, 85: 75, 86: 76, 87: 77, 88: 78, 89: 79, 90: 80}  # noqa
    """
    Mapping from the incontinuous COCO category id to an id in [1, #category]
    For your own dataset, this should usually be an identity mapping.
    """

    # 80 names for COCO
    class_names = [
        "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]  # noqa

    def __init__(self, basedir, name):
        self.name = name
        self._imgdir = os.path.realpath(os.path.join(
            basedir, self._INSTANCE_TO_BASEDIR.get(name, name)))
        assert os.path.isdir(self._imgdir), self._imgdir
        annotation_file = os.path.join(
            basedir, 'annotations/instances_{}.json'.format(name))
        assert os.path.isfile(annotation_file), annotation_file

        from pycocotools.coco import COCO
        self.coco = COCO(annotation_file)
        logger.info("Instances loaded from {}.".format(annotation_file))

    # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
    def print_coco_metrics(self, json_file):
        """
        Args:
            json_file (str): path to the results json file in coco format
        Returns:
            dict: the evaluation metrics
        """
        from pycocotools.cocoeval import COCOeval
        ret = {}
        cocoDt = self.coco.loadRes(json_file)
        cocoEval = COCOeval(self.coco, cocoDt, 'bbox')
        cocoEval.evaluate()
        cocoEval.accumulate()
        cocoEval.summarize()
        fields = ['IoU=0.5:0.95', 'IoU=0.5', 'IoU=0.75', 'small', 'medium', 'large']
        for k in range(6):
            ret['mAP(bbox)/' + fields[k]] = cocoEval.stats[k]

        json_obj = json.load(open(json_file))
        if len(json_obj) > 0 and 'segmentation' in json_obj[0]:
            cocoEval = COCOeval(self.coco, cocoDt, 'segm')
            cocoEval.evaluate()
            cocoEval.accumulate()
            cocoEval.summarize()
            for k in range(6):
                ret['mAP(segm)/' + fields[k]] = cocoEval.stats[k]
        return ret

    def load(self, add_gt=True, add_mask=False):
        """
        Args:
            add_gt: whether to add ground truth bounding box annotations to the dicts
            add_mask: whether to also add ground truth mask

        Returns:
            a list of dict, each has keys including:
                'height', 'width', 'id', 'file_name',
                and (if add_gt is True) 'boxes', 'class', 'is_crowd', and optionally
                'segmentation'.
        """
        if add_mask:
            assert add_gt
        with timed_operation('Load Groundtruth Boxes for {}'.format(self.name)):
            img_ids = self.coco.getImgIds()
            img_ids.sort()
            # list of dict, each has keys: height,width,id,file_name
            imgs = self.coco.loadImgs(img_ids)

            for img in tqdm.tqdm(imgs):
                self._use_absolute_file_name(img)
                if add_gt:
                    self._add_detection_gt(img, add_mask)
            return imgs

    def _use_absolute_file_name(self, img):
        """
        Change relative filename to abosolute file name.
        """
        img['file_name'] = os.path.join(
            self._imgdir, img['file_name'])
        assert os.path.isfile(img['file_name']), img['file_name']

    def _add_detection_gt(self, img, add_mask):
        """
        Add 'boxes', 'class', 'is_crowd' of this image to the dict, used by detection.
        If add_mask is True, also add 'segmentation' in coco poly format.
        """
        # ann_ids = self.coco.getAnnIds(imgIds=img['id'])
        # objs = self.coco.loadAnns(ann_ids)
        objs = self.coco.imgToAnns[img['id']]  # equivalent but faster than the above two lines

        # clean-up boxes
        valid_objs = []
        width = img['width']
        height = img['height']
        for objid, obj in enumerate(objs):
            if obj.get('ignore', 0) == 1:
                continue
            x1, y1, w, h = obj['bbox']
            # bbox is originally in float
            # x1/y1 means upper-left corner and w/h means true w/h. This can be verified by segmentation pixels.
            # But we do make an assumption here that (0.0, 0.0) is upper-left corner of the first pixel

            x1 = np.clip(float(x1), 0, width)
            y1 = np.clip(float(y1), 0, height)
            w = np.clip(float(x1 + w), 0, width) - x1
            h = np.clip(float(y1 + h), 0, height) - y1
            # Require non-zero seg area and more than 1x1 box size
            if obj['area'] > 1 and w > 0 and h > 0 and w * h >= 4:
                obj['bbox'] = [x1, y1, x1 + w, y1 + h]
                valid_objs.append(obj)

                if add_mask:
                    segs = obj['segmentation']
                    if not isinstance(segs, list):
                        assert obj['iscrowd'] == 1
                        obj['segmentation'] = None
                    else:
                        valid_segs = [np.asarray(p).reshape(-1, 2).astype('float32') for p in segs if len(p) >= 6]
                        if len(valid_segs) == 0:
                            logger.error("Object {} in image {} has no valid polygons!".format(objid, img['file_name']))
                        elif len(valid_segs) < len(segs):
                            logger.warn("Object {} in image {} has invalid polygons!".format(objid, img['file_name']))

                        obj['segmentation'] = valid_segs

        # all geometrically-valid boxes are returned
        boxes = np.asarray([obj['bbox'] for obj in valid_objs], dtype='float32')  # (n, 4)
        cls = np.asarray([
            self.COCO_id_to_category_id[obj['category_id']]
            for obj in valid_objs], dtype='int32')  # (n,)
        is_crowd = np.asarray([obj['iscrowd'] for obj in valid_objs], dtype='int8')

        # add the keys
        img['boxes'] = boxes        # nx4
        img['class'] = cls          # n, always >0
        img['is_crowd'] = is_crowd  # n,
        if add_mask:
            # also required to be float32
            img['segmentation'] = [
                obj['segmentation'] for obj in valid_objs]

    @staticmethod
    def load_many(basedir, names, add_gt=True, add_mask=False):
        """
        Load and merges several instance files together.

        Returns the same format as :meth:`COCODetection.load`.
        """
        if not isinstance(names, (list, tuple)):
            names = [names]
        ret = []
        for n in names:
            coco = COCODetection(basedir, n)
            ret.extend(coco.load(add_gt, add_mask=add_mask))
        return ret

class DetectionDataset(object):
    """
    A singleton to load datasets, evaluate results, and provide metadata.

    To use your own dataset that's not in COCO format, rewrite all methods of this class.
    """
    def __init__(self):
        """
        This function is responsible for setting the dataset-specific
        attributes in both cfg and self.
        """
        self.num_category = cfg.DATA.NUM_CATEGORY = len(COCODetection.class_names)
        self.num_classes = self.num_category + 1
        self.class_names = cfg.DATA.CLASS_NAMES = ["BG"] + COCODetection.class_names

    def load_training_roidbs(self, names):
        """
        Args:
            names (list[str]): name of the training datasets, e.g.  ['train2014', 'valminusminival2014']

        Returns:
            roidbs (list[dict]):

        Produce "roidbs" as a list of dict, each dict corresponds to one image with k>=0 instances.
        and the following keys are expected for training:

        height, width: integer
        file_name: str, full path to the image
        boxes: numpy array of kx4 floats, each row is [x1, y1, x2, y2]
        class: numpy array of k integers, in the range of [1, #categories], NOT [0, #categories)
        is_crowd: k booleans. Use k False if you don't know what it means.
        segmentation: k lists of numpy arrays (one for each instance).
            Each list of numpy arrays corresponds to the mask for one instance.
            Each numpy array in the list is a polygon of shape Nx2,
            because one mask can be represented by N polygons.

            If your segmentation annotations are originally masks rather than polygons,
            either convert it, or the augmentation will need to be changed or skipped accordingly.

            Include this field only if training Mask R-CNN.
        """
        return COCODetection.load_many(
            cfg.DATA.BASEDIR, cfg.DATA.TRAIN, add_gt=True, add_mask=cfg.MODE_MASK)

    def load_inference_roidbs(self, name):
        """
        Args:
            name (str): name of one inference dataset, e.g. 'minival2014'

        Returns:
            roidbs (list[dict]):

            Each dict corresponds to one image to run inference on. The
            following keys in the dict are expected:

            file_name (str): full path to the image
            id (str): an id for the image. The inference results will be stored with this id.
        """
        return COCODetection.load_many(cfg.DATA.BASEDIR, name, add_gt=False)

    def eval_or_save_inference_results(self, results, dataset, output=None):
        """
        Args:
            results (list[dict]): the inference results as dicts.
                Each dict corresponds to one __instance__. It contains the following keys:

                image_id (str): the id that matches `load_inference_roidbs`.
                category_id (int): the category prediction, in range [1, #category]
                bbox (list[float]): x1, y1, x2, y2
                score (float):
                segmentation: the segmentation mask in COCO's rle format.

            dataset (str): the name of the dataset to evaluate.
            output (str): the output file to optionally save the results to.

        Returns:
            dict: the evaluation results.
        """
        continuous_id_to_COCO_id = {v: k for k, v in COCODetection.COCO_id_to_category_id.items()}
        for res in results:
            # convert to COCO's incontinuous category id
            res['category_id'] = continuous_id_to_COCO_id[res['category_id']]
            # COCO expects results in xywh format
            box = res['bbox']
            box[2] -= box[0]
            box[3] -= box[1]
            res['bbox'] = [round(float(x), 3) for x in box]

        assert output is not None, "COCO evaluation requires an output file!"
        with open(output, 'w') as f:
            json.dump(results, f)
        if len(output):
            # sometimes may crash if the results are empty?
            return COCODetection(cfg.DATA.BASEDIR, dataset).print_coco_metrics(output)
        else:
            return {}

    # code for singleton:
    _instance = None

    def __new__(cls):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls)
        return cls._instance

if __name__ == '__main__':
    c = COCODetection(cfg.DATA.BASEDIR, 'train2014')
    gt_boxes = c.load(add_gt=True, add_mask=True)
    print("#Images:", len(gt_boxes))
<|endoftext|>


<commit_before>#!/usr/bin/env python

###
# Copyright (c) 2003, Brett Kelly
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * 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.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# 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.
###

"""
A complete messaging system that allows users to leave 'notes' for other
users that can be retrieved later.
"""

from baseplugin import *

import time
import os.path

import sqlite

import conf
import debug
import utils
import ircdb
import ircmsgs
import privmsgs
import ircutils
import callbacks

dbfilename = os.path.join(conf.dataDir, 'Notes.db')

example = utils.wrapLines("""
<jemfinch> @list Notes
<supybot> note, notes, oldnotes, sendnote
<jemfinch> @notes
<supybot> You have no unread notes.
<jemfinch> @oldnotes
<supybot> 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 15, 17, 18, 19, 25, 26, 27, 28, 37, 40, 41, 47
<jemfinch> @note 1
<supybot> I read their site, lurk on their forums and help out with the dc competitions (Sent by jamessan 18 weeks, 1 day, 6 hours, 46 minutes, and 53 seconds ago)
<jemfinch> @note 28
<supybot> er, you might want to change the ChannelStats module to ChannelDB in your conf file as well (Sent by Strike 2 weeks, 2 days, 20 hours, 11 minutes, and 58 seconds ago)
<jemfinch> @sendnote jemfinch hey, this is a note from yourself.
<supybot> The operation succeeded.
* jemfinch blah blah (he'll tell me I have unread notes)
<supybot> You have 1 unread note; 1 that I haven't told you about before now..
<jemfinch> @notes
<supybot> #49 from jemfinch
<jemfinch> @note 49
<supybot> hey, this is a note from yourself. (Sent by jemfinch 20 seconds ago)
""")

class Notes(callbacks.Privmsg):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        self.makeDB(dbfilename)

    def makeDB(self, filename):
        "create Notes database and tables"
        if os.path.exists(filename):
            self.db = sqlite.connect(filename)
            return
        self.db = sqlite.connect(filename, converters={'bool': bool})
        cursor = self.db.cursor()
        cursor.execute("""CREATE TABLE users (
                               id INTEGER PRIMARY KEY,
                               name TEXT UNIQUE ON CONFLICT IGNORE
                               )""")
        cursor.execute("""CREATE TABLE notes (
                               id INTEGER PRIMARY KEY,
                               from_id INTEGER,
                               to_id INTEGER,
                               added_at TIMESTAMP,
                               notified BOOLEAN,
                               read BOOLEAN,
                               public BOOLEAN,
                               note TEXT
                               )""")
        self.db.commit()

    def _addUser(self, username):
        "Not callable from channel, used to add users to database."
        cursor = self.db.cursor()
        cursor.execute('INSERT INTO users VALUES (NULL, %s)', username)
        self.db.commit()

    def getUserId(self, username):
        "Returns the user id matching the given username from the users table."
        cursor = self.db.cursor()
        cursor.execute('SELECT id FROM users where name=%s', username)
        if cursor.rowcount != 0:
            return cursor.fetchone()[0]
        else:
            raise KeyError, username

    def getUserName(self, userid):
        "Returns the username matching the given user id from the users table."
        cursor = self.db.cursor()
        cursor.execute('SELECT name FROM users WHERE id=%s', userid)
        if cursor.rowcount != 0:
            return cursor.fetchone()[0]
        else:
            raise KeyError, userid

    def setAsRead(self, noteid):
        "Changes a message's 'read' value to true in the notes table."
        cursor = self.db.cursor()
        cursor.execute("""UPDATE notes
                               SET read=1, notified=1
                               WHERE id=%s""", noteid)
        self.db.commit()

    def die(self):
        "Called when module is unloaded/reloaded."
        self.db.commit()
        self.db.close()
        del self.db

    def doPrivmsg(self, irc, msg):
        try:
            name = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            callbacks.Privmsg.doPrivmsg(self, irc, msg)
        cursor = self.db.cursor()
        cursor.execute("""SELECT COUNT(*) FROM notes, users
                          WHERE users.name=%s AND
                                notes.to_id=users.id AND
                                notified=0""", name)
        unnotified = int(cursor.fetchone()[0])
        if unnotified != 0:
            cursor.execute("""SELECT COUNT(*) FROM notes, users
                              WHERE users.name=%s AND
                                    notes.to_id=users.id AND
                                    read=0""", name)
            unread = int(cursor.fetchone()[0])
            s = 'You have %s; ' \
                '%s that I haven\'t told you about before now..' % \
                (utils.nItems(unread, 'note', 'unread'), unnotified)
            irc.queueMsg(ircmsgs.privmsg(msg.nick, s))
            cursor.execute("""UPDATE notes
                              SET notified=1
                              WHERE notes.to_id=(SELECT id
                                                 FROM users
                                                 WHERE name=%s)""", name)
        callbacks.Privmsg.doPrivmsg(self, irc, msg)

    def sendnote(self, irc, msg, args):
        """<recipient> <text>

        Sends a new note to the user specified.
        """
        (name, note) = privmsgs.getArgs(args, needed=2)
        sender = msg.nick
        if ircdb.users.hasUser(name):
            recipient = name
        else:
            n = irc.state.nickToHostmask(name)
            recipient = ircdb.users.getUserName(n)
        self._addUser(sender)
        self._addUser(recipient)
        senderId = self.getUserId(sender)
        recipId = self.getUserId(recipient)
        if ircutils.isChannel(msg.args[0]):
            public = 1
        else:
            public = 0
        cursor = self.db.cursor()
        cursor.execute("""INSERT INTO notes VALUES
                               (NULL, %s, %s, %s, 0, 0, %s, %s)""",
                               senderId, recipId, int(time.time()),
                               public, note)
        self.db.commit()
        irc.reply(msg, conf.replySuccess)

    def note(self, irc, msg, args):
        """<note id>

        Retrieves a single note by unique note id.
        """
        noteid = privmsgs.getArgs(args)
        try:
            sender = ircdb.users.getUserName(msg.prefix)
            senderId = self.getUserId(sender)
        except KeyError:
            irc.error(msg, conf.replyNoUser)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.note, notes.to_id, notes.from_id,
                                      notes.added_at, notes.public
                               FROM users, notes
                               WHERE users.name=%s AND
                                     notes.to_id=users.id AND
                                     notes.id=%s
                               LIMIT 1""", sender, noteid)
        if cursor.rowcount == 0:
            irc.error(msg, 'That\'s not a valid note id.')
            return
        (note, to_id, from_id, added_at, public) = cursor.fetchone()
        author = self.getUserName(from_id)
        if senderId != to_id:
            irc.error(msg, 'You are not the recipient of note %s.' % noteid)
            return
        public = int(public)
        elapsed = utils.timeElapsed(time.time() - int(added_at))
        newnote = "%s (Sent by %s %s ago)" % (note, author, elapsed)
        if public:
            irc.reply(msg, newnote)
        else:
            irc.queueMsg(ircmsgs.privmsg(msg.nick, newnote))
        self.setAsRead(noteid)

    def notes(self, irc, msg, args):
        """takes no arguments

        Retrieves all your unread notes.
        """
        try:
            sender = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            irc.error(msg, conf.replyNoUser)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.id, notes.from_id,
                                 notes.public, notes.read
                          FROM users, notes
                          WHERE users.name=%s AND
                                notes.to_id=users.id AND
                                notes.read=0""", sender)
        count = cursor.rowcount
        notes = cursor.fetchall()
        L = []
        if count == 0:
            irc.reply(msg, 'You have no unread notes.')
        else:
            for (id, from_id, public, read) in notes:
                if not int(read):
                    sender = self.getUserName(from_id)
                    if int(public) or not ircutils.isChannel(msg.args[0]):
                        L.append(r'#%s from %s' % (id, sender))
                    else:
                        L.append(r'#%s (private)' % id)
            irc.reply(msg, utils.commaAndify(L))

    def oldnotes(self, irc, msg, args):
        """takes no arguments

        Returns a list of your most recent old notes.
        """
        try:
            sender = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            irc.error(msg, conf.replyNoUser)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.id FROM users, notes
                          WHERE notes.to_id=users.id AND
                                users.name=%s AND
                                notes.read=1""", sender)
        if cursor.rowcount == 0:
            irc.reply(msg, 'I couldn\'t find any notes for your user.')
        else:
            ids = [str(t[0]) for t in cursor.fetchall()]
            ids.reverse()
            irc.reply(msg, utils.commaAndify(ids))

Class = Notes

# vim: shiftwidth=4 tabstop=8 expandtab textwidth=78:
<commit_msg>Fixed doPrivmsg and added more useful error messages<commit_after>#!/usr/bin/env python

###
# Copyright (c) 2003, Brett Kelly
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * 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.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# 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.
###

"""
A complete messaging system that allows users to leave 'notes' for other
users that can be retrieved later.
"""

from baseplugin import *

import time
import os.path

import sqlite

import conf
import debug
import utils
import ircdb
import ircmsgs
import privmsgs
import ircutils
import callbacks

dbfilename = os.path.join(conf.dataDir, 'Notes.db')

example = utils.wrapLines("""
<jemfinch> @list Notes
<supybot> note, notes, oldnotes, sendnote
<jemfinch> @notes
<supybot> You have no unread notes.
<jemfinch> @oldnotes
<supybot> 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 15, 17, 18, 19, 25, 26, 27, 28, 37, 40, 41, 47
<jemfinch> @note 1
<supybot> I read their site, lurk on their forums and help out with the dc competitions (Sent by jamessan 18 weeks, 1 day, 6 hours, 46 minutes, and 53 seconds ago)
<jemfinch> @note 28
<supybot> er, you might want to change the ChannelStats module to ChannelDB in your conf file as well (Sent by Strike 2 weeks, 2 days, 20 hours, 11 minutes, and 58 seconds ago)
<jemfinch> @sendnote jemfinch hey, this is a note from yourself.
<supybot> The operation succeeded.
* jemfinch blah blah (he'll tell me I have unread notes)
<supybot> You have 1 unread note; 1 that I haven't told you about before now..
<jemfinch> @notes
<supybot> #49 from jemfinch
<jemfinch> @note 49
<supybot> hey, this is a note from yourself. (Sent by jemfinch 20 seconds ago)
""")

class Notes(callbacks.Privmsg):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        self.makeDB(dbfilename)

    def makeDB(self, filename):
        "create Notes database and tables"
        if os.path.exists(filename):
            self.db = sqlite.connect(filename)
            return
        self.db = sqlite.connect(filename, converters={'bool': bool})
        cursor = self.db.cursor()
        cursor.execute("""CREATE TABLE users (
                               id INTEGER PRIMARY KEY,
                               name TEXT UNIQUE ON CONFLICT IGNORE
                               )""")
        cursor.execute("""CREATE TABLE notes (
                               id INTEGER PRIMARY KEY,
                               from_id INTEGER,
                               to_id INTEGER,
                               added_at TIMESTAMP,
                               notified BOOLEAN,
                               read BOOLEAN,
                               public BOOLEAN,
                               note TEXT
                               )""")
        self.db.commit()

    def _addUser(self, username):
        "Not callable from channel, used to add users to database."
        cursor = self.db.cursor()
        cursor.execute('INSERT INTO users VALUES (NULL, %s)', username)
        self.db.commit()

    def getUserId(self, username):
        "Returns the user id matching the given username from the users table."
        cursor = self.db.cursor()
        cursor.execute('SELECT id FROM users where name=%s', username)
        if cursor.rowcount != 0:
            return cursor.fetchone()[0]
        else:
            raise KeyError, username

    def getUserName(self, userid):
        "Returns the username matching the given user id from the users table."
        cursor = self.db.cursor()
        cursor.execute('SELECT name FROM users WHERE id=%s', userid)
        if cursor.rowcount != 0:
            return cursor.fetchone()[0]
        else:
            raise KeyError, userid

    def setAsRead(self, noteid):
        "Changes a message's 'read' value to true in the notes table."
        cursor = self.db.cursor()
        cursor.execute("""UPDATE notes
                               SET read=1, notified=1
                               WHERE id=%s""", noteid)
        self.db.commit()

    def die(self):
        "Called when module is unloaded/reloaded."
        self.db.commit()
        self.db.close()
        del self.db

    def doPrivmsg(self, irc, msg):
        try:
            name = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            callbacks.Privmsg.doPrivmsg(self, irc, msg)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT COUNT(*) FROM notes, users
                          WHERE users.name=%s AND
                                notes.to_id=users.id AND
                                notified=0""", name)
        unnotified = int(cursor.fetchone()[0])
        if unnotified != 0:
            cursor.execute("""SELECT COUNT(*) FROM notes, users
                              WHERE users.name=%s AND
                                    notes.to_id=users.id AND
                                    read=0""", name)
            unread = int(cursor.fetchone()[0])
            s = 'You have %s; ' \
                '%s that I haven\'t told you about before now..' % \
                (utils.nItems(unread, 'note', 'unread'), unnotified)
            irc.queueMsg(ircmsgs.privmsg(msg.nick, s))
            cursor.execute("""UPDATE notes
                              SET notified=1
                              WHERE notes.to_id=(SELECT id
                                                 FROM users
                                                 WHERE name=%s)""", name)
        callbacks.Privmsg.doPrivmsg(self, irc, msg)

    def sendnote(self, irc, msg, args):
        """<recipient> <text>

        Sends a new note to the user specified.
        """
        (name, note) = privmsgs.getArgs(args, needed=2)
        sender = msg.nick
        if ircdb.users.hasUser(name):
            recipient = name
        else:
            n = irc.state.nickToHostmask(name)
            recipient = ircdb.users.getUserName(n)
        self._addUser(sender)
        self._addUser(recipient)
        senderId = self.getUserId(sender)
        recipId = self.getUserId(recipient)
        if ircutils.isChannel(msg.args[0]):
            public = 1
        else:
            public = 0
        cursor = self.db.cursor()
        cursor.execute("""INSERT INTO notes VALUES
                               (NULL, %s, %s, %s, 0, 0, %s, %s)""",
                               senderId, recipId, int(time.time()),
                               public, note)
        self.db.commit()
        irc.reply(msg, conf.replySuccess)

    def note(self, irc, msg, args):
        """<note id>

        Retrieves a single note by unique note id.
        """
        noteid = privmsgs.getArgs(args)
        try:
            sender = ircdb.users.getUserName(msg.prefix)
            senderId = self.getUserId(sender)
        except KeyError:
            irc.error(msg, conf.replyNotRegistered)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.note, notes.to_id, notes.from_id,
                                      notes.added_at, notes.public
                               FROM users, notes
                               WHERE users.name=%s AND
                                     notes.to_id=users.id AND
                                     notes.id=%s
                               LIMIT 1""", sender, noteid)
        if cursor.rowcount == 0:
            irc.error(msg, 'That\'s not a valid note id.')
            return
        (note, to_id, from_id, added_at, public) = cursor.fetchone()
        author = self.getUserName(from_id)
        if senderId != to_id:
            irc.error(msg, 'You are not the recipient of note %s.' % noteid)
            return
        public = int(public)
        elapsed = utils.timeElapsed(time.time() - int(added_at))
        newnote = "%s (Sent by %s %s ago)" % (note, author, elapsed)
        if public:
            irc.reply(msg, newnote)
        else:
            irc.queueMsg(ircmsgs.privmsg(msg.nick, newnote))
        self.setAsRead(noteid)

    def notes(self, irc, msg, args):
        """takes no arguments

        Retrieves all your unread notes.
        """
        try:
            sender = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            irc.error(msg, conf.replyNotRegistered)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.id, notes.from_id,
                                 notes.public, notes.read
                          FROM users, notes
                          WHERE users.name=%s AND
                                notes.to_id=users.id AND
                                notes.read=0""", sender)
        count = cursor.rowcount
        notes = cursor.fetchall()
        L = []
        if count == 0:
            irc.reply(msg, 'You have no unread notes.')
        else:
            for (id, from_id, public, read) in notes:
                if not int(read):
                    sender = self.getUserName(from_id)
                    if int(public) or not ircutils.isChannel(msg.args[0]):
                        L.append(r'#%s from %s' % (id, sender))
                    else:
                        L.append(r'#%s (private)' % id)
            irc.reply(msg, utils.commaAndify(L))

    def oldnotes(self, irc, msg, args):
        """takes no arguments

        Returns a list of your most recent old notes.
        """
        try:
            sender = ircdb.users.getUserName(msg.prefix)
        except KeyError:
            irc.error(msg, conf.replyNotRegistered)
            return
        cursor = self.db.cursor()
        cursor.execute("""SELECT notes.id FROM users, notes
                          WHERE notes.to_id=users.id AND
                                users.name=%s AND
                                notes.read=1""", sender)
        if cursor.rowcount == 0:
            irc.reply(msg, 'I couldn\'t find any notes for your user.')
        else:
            ids = [str(t[0]) for t in cursor.fetchall()]
            ids.reverse()
            irc.reply(msg, utils.commaAndify(ids))

Class = Notes

# vim: shiftwidth=4 tabstop=8 expandtab textwidth=78:
<|endoftext|>


<commit_before>#!/usr/bin/python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * 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.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# 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.
###

"""
This handles interesting things to do with a dictionary (words) database.
"""

import supybot
import plugins

import os
import copy
import string
import time
import random

import sqlite

import conf
import utils
import privmsgs
import callbacks
import configurable
import ircutils

def configure(onStart, afterConnect, advanced):
    # This will be called by setup.py to configure this module.  onStart and
    # afterConnect are both lists.  Append to onStart the commands you would
    # like to be run when the bot is started; append to afterConnect the
    # commands you would like to be run when the bot has finished connecting.
    from questions import expect, anything, something, yn
    onStart.append('load Words')

class WordsDB(plugins.DBHandler):
    def makeDb(self, filename):
        if os.path.exists(filename):
            db = sqlite.connect(filename)
        else:
            db = sqlite.connect(filename, converters={'bool': bool})
            cursor = db.cursor()
            cursor.execute("""CREATE TABLE words (
                              id INTEGER PRIMARY KEY,
                              word TEXT UNIQUE ON CONFLICT IGNORE,
                              sorted_word_id INTEGER)""")
            cursor.execute("""CREATE TABLE sorted_words (
                              id INTEGER PRIMARY KEY,
                              word TEXT UNIQUE ON CONFLICT IGNORE)""")
            cursor.execute("""CREATE INDEX sorted_word_id
                              ON words (sorted_word_id)""")
            cursor.execute("""CREATE INDEX sorted_words_word
                              ON sorted_words (word)""")
            db.commit()
        return db

def addWord(db, word, commit=False):
    word = word.strip().lower()
    if word.translate(string.ascii, string.ascii_letters):
        raise ValueError, 'Invalid word: %r' % word
    L = list(word)
    L.sort()
    sorted = ''.join(L)
    cursor = db.cursor()
    cursor.execute("""INSERT INTO sorted_words VALUES (NULL, %s)""", sorted)
    cursor.execute("""INSERT INTO words VALUES (NULL, %s,
                      (SELECT id FROM sorted_words
                       WHERE word=%s))""", word, sorted)
    if commit:
        db.commit()

class HangmanGame:
    def __init__(self):
        self.gameOn = False
        self.timeout = 0
        self.timeGuess = 0
        self.tries = 0
        self.prefix = ''
        self.guessed = False
        self.unused = ''
        self.hidden = ''
        self.guess = ''

    def getWord(self, dbHandler):
        db = dbHandler.getDb()
        cur = db.cursor()
        cur.execute("""SELECT word FROM words ORDER BY random() LIMIT 1""")
        word = cur.fetchone()[0]
        return word

    def letterPositions(self, letter, word):
        """
        Returns a list containing the positions of letter in word.
        """
        lst = []
        for i in xrange(len(word)):
            if word[i] == letter:
                lst.append(i)
        return lst

    def addLetter(self, letter, word, pos):
        """
        Replaces all characters of word at positions contained in pos
        by letter.
        """
        newWord = []
        for i in xrange(len(word)):
            if i in pos:
                newWord.append(letter)
            else:
                newWord.append(word[i])
        return ''.join(newWord)

    def triesLeft(self, n):
        """
        Returns the number of tries and the correctly pluralized try/tries
        """
        return utils.nItems('try', n)

    def letterArticle(self, letter):
        """
        Returns 'a' or 'an' to match the letter that will come after
        """
        anLetter = 'aefhilmnorsx'
        if letter in anLetter:
            return 'an'
        else:
            return 'a'

class Words(callbacks.Privmsg, configurable.Mixin):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        configurable.Mixin.__init__(self)
        self.dbHandler = WordsDB(os.path.join(conf.dataDir, 'Words'))
        try:
            dictfile = os.path.join(conf.dataDir, 'dict')
            self.wordList = file(dictfile).readlines()
            self.gotDictFile = True
        except IOError:
            self.gotDictFile = False
        self.gameon = False

    def add(self, irc, msg, args):
        """<word> [<word>]

        Adds a word or words to the database of words.  This database is used
        for the other commands in this plugin.
        """
        if not args:
            raise callbacks.ArgumentError
        for word in args:
            if word.translate(string.ascii, string.ascii_letters):
                irc.error(msg, 'Word must contain only letters.')
                return
            else:
                addWord(self.dbHandler.getDb(), word, commit=True)
        irc.replySuccess()

    def crossword(self, irc, msg, args):
        """<word>

        Gives the possible crossword completions for <word>; use underscores
        ('_') to denote blank spaces.
        """
        word = privmsgs.getArgs(args).lower()
        db = self.dbHandler.getDb()
        cursor = db.cursor()
        if '%' in word:
            irc.error(msg, '"%" isn\'t allowed in the word.')
            return
        cursor.execute("""SELECT word FROM words
                          WHERE word LIKE %s
                          ORDER BY word""", word)
        words = [t[0] for t in cursor.fetchall()]
        if words:
            irc.reply(msg, utils.commaAndify(words))
        else:
            irc.reply(msg, 'No matching words were found.')

    def anagram(self, irc, msg, args):
        """<word>

        Using the words database, determines if a word has any anagrams.
        """
        word = privmsgs.getArgs(args).strip().lower()
        db = self.dbHandler.getDb()
        cursor = db.cursor()
        L = list(word.lower())
        L.sort()
        sorted = ''.join(L)
        cursor.execute("""SELECT id FROM sorted_words WHERE word=%s""", sorted)
        if cursor.rowcount == 0:
            irc.reply(msg, 'That word has no anagrams I could find.')
        else:
            id = cursor.fetchone()[0]
            cursor.execute("""SELECT words.word FROM words
                              WHERE sorted_word_id=%s""", id)
            if cursor.rowcount > 1:
                words = [t[0] for t in cursor.fetchall()]
                irc.reply(msg, utils.commaAndify(words))
            else:
                irc.reply(msg, 'That word has no anagrams I could find.')

    ###
    # HANGMAN
    ###
    configurables = configurable.Dictionary(
        [('tries', configurable.IntType, 6, 'Number of tries to guess a word'),
         ('prefix', configurable.StrType, '-= HANGMAN =-',
             'Prefix string of the hangman plugin'),
         ('timeout', configurable.IntType, 300, 'Time before a game times out')]
    )
    dicturl = 'http://www.unixhideout.com/freebsd/share/dict/words'
    games = ircutils.IrcDict()
    validLetters = [chr(c) for c in range(ord('a'), ord('z')+1)]

    def endGame(self, channel):
        self.games[channel].gameOn = False

    def letters(self, irc, msg, args):
        """takes no arguments

        Returns unused letters
        """
        channel = msg.args[0]
        game = self.games[channel]
        if not game.gameOn:
            irc.error(msg, 'There is no hangman game going on right now.')
            return
        irc.reply(msg, '%s %s' % (game.prefix, ' '.join(game.unused)))

    def hangman(self, irc, msg, args):
        """takes no arguments

        Creates a new game of hangman
        """
        channel = msg.args[0]
        # Fill our dictionary of games
        if channel not in self.games:
            self.games[channel] = HangmanGame()
        game = self.games[channel]
        # We only start a new game if no other game is going on right now
        if not game.gameOn:
            game.gameOn = True
            game.timeout = self.configurables.get('timeout', channel)
            game.timeGuess = time.time()
            game.tries = self.configurables.get('tries', channel)
            game.prefix = self.configurables.get('prefix', channel) + ' '
            game.guessed = False
            game.unused = copy.copy(self.validLetters)
            game.hidden = game.getWord(self.dbHandler)
            game.guess = '_' * len(game.hidden)
            irc.reply(msg, '%sOkay ladies and gentlemen, you have '
                      'a %s-letter word to find, you have %s!' %
                      (game.prefix, len(game.hidden),
                      game.triesLeft(game.tries)), prefixName=False)
        # So, a game is going on, but let's see if it's timed out.  If it is
        # we create a new one, otherwise we inform the user
        else:
            secondsEllapsed = time.time() - game.timeGuess
            if secondsEllapsed > game.timeout:
                self.endGame(channel)
                self.hangman(irc, msg, args)
            else:
                irc.error(msg, 'Sorry, there is already a game going on.  '
                        '%s left before timeout.' % utils.nItems('second',
                            int(game.timeout - secondsEllapsed)))

    def guess(self, irc, msg, args):
        """<single letter>|<whole word>

        Try to guess a single letter or the whole word.  If you try to guess
        the whole word and you are wrong, you automatically lose.
        """
        channel = msg.args[0]
        game = self.games[channel]
        if not game.gameOn:
            irc.error(msg, 'There is no hangman game going on right now.')
            return
        letter = privmsgs.getArgs(args)
        game.timeGuess = time.time()
        # User input a valid letter that hasn't been already tried
        if letter in game.unused:
            del game.unused[game.unused.index(letter)]
            if letter in game.hidden:
                irc.reply(msg, '%sYes, there is %s %s' % (game.prefix,
                    game.letterArticle(letter), `letter`), prefixName=False)
                game.guess = game.addLetter(letter, game.guess,
                        game.letterPositions(letter, game.hidden))
                if game.guess == game.hidden:
                    game.guessed = True
            else:
                irc.reply(msg,'%sNo, there is no %s' % (game.prefix,`letter`),
                        prefixName=False)
                game.tries -= 1
            irc.reply(msg, '%s%s (%s left)' % (game.prefix, game.guess,
                game.triesLeft(game.tries)), prefixName=False)
        # User input a valid character that has already been tried
        elif letter in self.validLetters:
            irc.error(msg, 'That letter has already been tried.')
        # User tries to guess the whole word or entered an invalid input
        else:
            # The length of the word tried by the user and that of the hidden
            # word are same, so we assume the user wants to guess the whole
            # word
            if len(letter) == len(game.hidden):
                if letter == game.hidden:
                    game.guessed = True
                else:
                    irc.reply(msg, '%syou did not guess the correct word '
                        'and you lose a try' % game.prefix, prefixName=False)
                    game.tries -= 1
            else:
                # User input an invalid character
                if len(letter) == 1:
                    irc.error(msg, 'That is not a valid character.')
                # User input an invalid word (len(try) != len(hidden))
                else:
                    irc.error(msg, 'That is not a valid word guess.')
        # Verify if the user won or lost
        if game.guessed and game.tries > 0:
            irc.reply(msg, '%sYou win! The word was indeed %s' %
                    (game.prefix, game.hidden), prefixName=False)
            self.endGame(channel)
        elif not game.guessed and game.tries == 0:
            irc.reply(msg, '%sYou lose! The word was %s' %
                    (game.prefix, game.hidden), prefixName=False)
            self.endGame(channel)
    ###
    # END HANGMAN
    ###

Class = Words

if __name__ == '__main__':
    import sys, log
    if len(sys.argv) < 2:
        fd = sys.stdin
    else:
        try:
            fd = file(sys.argv[1])
        except EnvironmentError, e:
            sys.stderr.write(str(e) + '\n')
            sys.exit(-1)
    db = WordsDB(os.path.join(conf.dataDir, 'Words')).getDb()
    cursor = db.cursor()
    cursor.execute("""PRAGMA cache_size=20000""")
    lineno = 0
    for line in fd:
        lineno += 1
        line = line.rstrip()
        try:
            addWord(db, line)
        except KeyboardInterrupt:
            sys.exit(-1)
        except Exception, e:
            sys.stderr.write('Error on line %s: %s\n' % (lineno, e))
    db.commit()

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<commit_msg>Fixed a bug<commit_after>#!/usr/bin/python

###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * 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.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# 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.
###

"""
This handles interesting things to do with a dictionary (words) database.
"""

import supybot
import plugins

import os
import copy
import string
import time
import random

import sqlite

import conf
import utils
import privmsgs
import callbacks
import configurable
import ircutils

def configure(onStart, afterConnect, advanced):
    # This will be called by setup.py to configure this module.  onStart and
    # afterConnect are both lists.  Append to onStart the commands you would
    # like to be run when the bot is started; append to afterConnect the
    # commands you would like to be run when the bot has finished connecting.
    from questions import expect, anything, something, yn
    onStart.append('load Words')

class WordsDB(plugins.DBHandler):
    def makeDb(self, filename):
        if os.path.exists(filename):
            db = sqlite.connect(filename)
        else:
            db = sqlite.connect(filename, converters={'bool': bool})
            cursor = db.cursor()
            cursor.execute("""CREATE TABLE words (
                              id INTEGER PRIMARY KEY,
                              word TEXT UNIQUE ON CONFLICT IGNORE,
                              sorted_word_id INTEGER)""")
            cursor.execute("""CREATE TABLE sorted_words (
                              id INTEGER PRIMARY KEY,
                              word TEXT UNIQUE ON CONFLICT IGNORE)""")
            cursor.execute("""CREATE INDEX sorted_word_id
                              ON words (sorted_word_id)""")
            cursor.execute("""CREATE INDEX sorted_words_word
                              ON sorted_words (word)""")
            db.commit()
        return db

def addWord(db, word, commit=False):
    word = word.strip().lower()
    if word.translate(string.ascii, string.ascii_letters):
        raise ValueError, 'Invalid word: %r' % word
    L = list(word)
    L.sort()
    sorted = ''.join(L)
    cursor = db.cursor()
    cursor.execute("""INSERT INTO sorted_words VALUES (NULL, %s)""", sorted)
    cursor.execute("""INSERT INTO words VALUES (NULL, %s,
                      (SELECT id FROM sorted_words
                       WHERE word=%s))""", word, sorted)
    if commit:
        db.commit()

class HangmanGame:
    def __init__(self):
        self.gameOn = False
        self.timeout = 0
        self.timeGuess = 0
        self.tries = 0
        self.prefix = ''
        self.guessed = False
        self.unused = ''
        self.hidden = ''
        self.guess = ''

    def getWord(self, dbHandler):
        db = dbHandler.getDb()
        cur = db.cursor()
        cur.execute("""SELECT word FROM words ORDER BY random() LIMIT 1""")
        word = cur.fetchone()[0]
        return word

    def letterPositions(self, letter, word):
        """
        Returns a list containing the positions of letter in word.
        """
        lst = []
        for i in xrange(len(word)):
            if word[i] == letter:
                lst.append(i)
        return lst

    def addLetter(self, letter, word, pos):
        """
        Replaces all characters of word at positions contained in pos
        by letter.
        """
        newWord = []
        for i in xrange(len(word)):
            if i in pos:
                newWord.append(letter)
            else:
                newWord.append(word[i])
        return ''.join(newWord)

    def triesLeft(self, n):
        """
        Returns the number of tries and the correctly pluralized try/tries
        """
        return utils.nItems('try', n)

    def letterArticle(self, letter):
        """
        Returns 'a' or 'an' to match the letter that will come after
        """
        anLetter = 'aefhilmnorsx'
        if letter in anLetter:
            return 'an'
        else:
            return 'a'

class Words(callbacks.Privmsg, configurable.Mixin):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        configurable.Mixin.__init__(self)
        self.dbHandler = WordsDB(os.path.join(conf.dataDir, 'Words'))
        try:
            dictfile = os.path.join(conf.dataDir, 'dict')
            self.wordList = file(dictfile).readlines()
            self.gotDictFile = True
        except IOError:
            self.gotDictFile = False
        self.gameon = False

    def add(self, irc, msg, args):
        """<word> [<word>]

        Adds a word or words to the database of words.  This database is used
        for the other commands in this plugin.
        """
        if not args:
            raise callbacks.ArgumentError
        for word in args:
            if word.translate(string.ascii, string.ascii_letters):
                irc.error('Word must contain only letters.')
                return
            else:
                addWord(self.dbHandler.getDb(), word, commit=True)
        irc.replySuccess()

    def crossword(self, irc, msg, args):
        """<word>

        Gives the possible crossword completions for <word>; use underscores
        ('_') to denote blank spaces.
        """
        word = privmsgs.getArgs(args).lower()
        db = self.dbHandler.getDb()
        cursor = db.cursor()
        if '%' in word:
            irc.error('"%" isn\'t allowed in the word.')
            return
        cursor.execute("""SELECT word FROM words
                          WHERE word LIKE %s
                          ORDER BY word""", word)
        words = [t[0] for t in cursor.fetchall()]
        if words:
            irc.reply(utils.commaAndify(words))
        else:
            irc.reply('No matching words were found.')

    def anagram(self, irc, msg, args):
        """<word>

        Using the words database, determines if a word has any anagrams.
        """
        word = privmsgs.getArgs(args).strip().lower()
        db = self.dbHandler.getDb()
        cursor = db.cursor()
        L = list(word.lower())
        L.sort()
        sorted = ''.join(L)
        cursor.execute("""SELECT id FROM sorted_words WHERE word=%s""", sorted)
        if cursor.rowcount == 0:
            irc.reply('That word has no anagrams I could find.')
        else:
            id = cursor.fetchone()[0]
            cursor.execute("""SELECT words.word FROM words
                              WHERE sorted_word_id=%s""", id)
            if cursor.rowcount > 1:
                words = [t[0] for t in cursor.fetchall()]
                irc.reply(utils.commaAndify(words))
            else:
                irc.reply('That word has no anagrams I could find.')

    ###
    # HANGMAN
    ###
    configurables = configurable.Dictionary(
        [('tries', configurable.IntType, 6, 'Number of tries to guess a word'),
         ('prefix', configurable.StrType, '-= HANGMAN =-',
             'Prefix string of the hangman plugin'),
         ('timeout', configurable.IntType, 300, 'Time before a game times out')]
    )
    dicturl = 'http://www.unixhideout.com/freebsd/share/dict/words'
    games = ircutils.IrcDict()
    validLetters = [chr(c) for c in range(ord('a'), ord('z')+1)]

    def endGame(self, channel):
        self.games[channel].gameOn = False

    def letters(self, irc, msg, args):
        """takes no arguments

        Returns unused letters
        """
        channel = msg.args[0]
        if channel in self.games:
            game = self.games[channel]
            if game.gameOn:
                irc.reply('%s %s' % (game.prefix, ' '.join(game.unused)))
                return
        irc.error('There is no hangman game going on right now.')

    def hangman(self, irc, msg, args):
        """takes no arguments

        Creates a new game of hangman
        """
        channel = msg.args[0]
        # Fill our dictionary of games
        if channel not in self.games:
            self.games[channel] = HangmanGame()
        game = self.games[channel]
        # We only start a new game if no other game is going on right now
        if not game.gameOn:
            game.gameOn = True
            game.timeout = self.configurables.get('timeout', channel)
            game.timeGuess = time.time()
            game.tries = self.configurables.get('tries', channel)
            game.prefix = self.configurables.get('prefix', channel) + ' '
            game.guessed = False
            game.unused = copy.copy(self.validLetters)
            game.hidden = game.getWord(self.dbHandler)
            game.guess = '_' * len(game.hidden)
            irc.reply('%sOkay ladies and gentlemen, you have '
                      'a %s-letter word to find, you have %s!' %
                      (game.prefix, len(game.hidden),
                      game.triesLeft(game.tries)), prefixName=False)
        # So, a game is going on, but let's see if it's timed out.  If it is
        # we create a new one, otherwise we inform the user
        else:
            secondsEllapsed = time.time() - game.timeGuess
            if secondsEllapsed > game.timeout:
                self.endGame(channel)
                self.hangman(irc, msg, args)
            else:
                irc.error('Sorry, there is already a game going on.  '
                        '%s left before timeout.' % utils.nItems('second',
                            int(game.timeout - secondsEllapsed)))

    def guess(self, irc, msg, args):
        """<single letter>|<whole word>

        Try to guess a single letter or the whole word.  If you try to guess
        the whole word and you are wrong, you automatically lose.
        """
        channel = msg.args[0]
        try:
            game = self.games[channel]
        except KeyError:
            irc.error('There is no hangman game going on right now.')
            return
        if not game.gameOn:
            irc.error('There is no hangman game going on right now.')
            return
        letter = privmsgs.getArgs(args)
        game.timeGuess = time.time()
        # User input a valid letter that hasn't been already tried
        if letter in game.unused:
            del game.unused[game.unused.index(letter)]
            if letter in game.hidden:
                irc.reply('%sYes, there is %s %s' % (game.prefix,
                    game.letterArticle(letter), `letter`), prefixName=False)
                game.guess = game.addLetter(letter, game.guess,
                        game.letterPositions(letter, game.hidden))
                if game.guess == game.hidden:
                    game.guessed = True
            else:
                irc.reply('%sNo, there is no %s' % (game.prefix,`letter`),
                        prefixName=False)
                game.tries -= 1
            irc.reply('%s%s (%s left)' % (game.prefix, game.guess,
                game.triesLeft(game.tries)), prefixName=False)
        # User input a valid character that has already been tried
        elif letter in self.validLetters:
            irc.error('That letter has already been tried.')
        # User tries to guess the whole word or entered an invalid input
        else:
            # The length of the word tried by the user and that of the hidden
            # word are same, so we assume the user wants to guess the whole
            # word
            if len(letter) == len(game.hidden):
                if letter == game.hidden:
                    game.guessed = True
                else:
                    irc.reply('%syou did not guess the correct word '
                        'and you lose a try' % game.prefix, prefixName=False)
                    game.tries -= 1
            else:
                # User input an invalid character
                if len(letter) == 1:
                    irc.error('That is not a valid character.')
                # User input an invalid word (len(try) != len(hidden))
                else:
                    irc.error('That is not a valid word guess.')
        # Verify if the user won or lost
        if game.guessed and game.tries > 0:
            irc.reply('%sYou win! The word was indeed %s' %
                    (game.prefix, game.hidden), prefixName=False)
            self.endGame(channel)
        elif not game.guessed and game.tries == 0:
            irc.reply('%sYou lose! The word was %s' %
                    (game.prefix, game.hidden), prefixName=False)
            self.endGame(channel)
    ###
    # END HANGMAN
    ###

Class = Words

if __name__ == '__main__':
    import sys, log
    if len(sys.argv) < 2:
        fd = sys.stdin
    else:
        try:
            fd = file(sys.argv[1])
        except EnvironmentError, e:
            sys.stderr.write(str(e) + '\n')
            sys.exit(-1)
    db = WordsDB(os.path.join(conf.dataDir, 'Words')).getDb()
    cursor = db.cursor()
    cursor.execute("""PRAGMA cache_size=20000""")
    lineno = 0
    for line in fd:
        lineno += 1
        line = line.rstrip()
        try:
            addWord(db, line)
        except KeyboardInterrupt:
            sys.exit(-1)
        except Exception, e:
            sys.stderr.write('Error on line %s: %s\n' % (lineno, e))
    db.commit()

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<|endoftext|>


<commit_before>import commands
import permissions
import discord
import requests
import random
import time
import json
from json import JSONDecodeError

client = None

def getData(endpoint, tags, limit=10):
    headers = {"user-agent":"[^_^]/1.0"}
    t = tags.pop(0)
    for x in tags:
        t+="+"+x
    session = requests.Session()
    session.headers.update(headers)

    response = session.get(endpoint.format(limit, t))

    j = response.json()

    return j

def downloadImage(url):
    headers = {"user-agent":"[^_^]/1.0"}
    session = requests.Session()
    session.headers.update(headers)

    file_response = session.get(url)
    file_extension = url[url.rfind(".")+1:]

    #https://stackoverflow.com/a/39217788
    data = file_response.content
    print(len(data))
    with open("out."+file_extension, 'wb') as f:
        f.write(data)
        f.close()

    return "out."+file_extension

def gelbooru(tags):
    j = getData("http://gelbooru.com/index.php?page=dapi&limit={0}&s=post&&q=index&json=1&tags={1}", tags)

    target = j[random.randint(0, len(j))]['file_url']
    return downloadImage(target)

#tags should be a list of desired tags
def e621(tags):
    j = getData("http://e621.net/post/index.json?limit={0}&tags={1}", tags)

    target = j[random.randint(0, len(j))]['file_url']
    return downloadImage(target)

functionMap = {"e621":e621, "gelbooru":gelbooru}

async def postRandom(booru, tags):
    try:
        out = functionMap[booru](tags)

        with open(out, "rb") as image:
            await client.send_file(triggerMessage.channel, image, filename=out)
    except IndexError:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. " + tokens[1] + " returned no search results.")
    except KeyError:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie. " + tokens[1] + " is not supported.\nSupported boorus: "+str(SUPPORTED))
    except JSONDecodeError:
        await client.send_message(triggerMessage.channel, "Oopsie Woopsie. Failed to decode json. " + tokens[1] + " returned an empty response, or something weird")
    except Exception as e:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. One of many possible disasters has occured. Try `!booru help`\nException: " + type(e).__name__)
        print(e) #hopefully this does something useful

@commands.registerEventHandler(name="booru", exclusivity="global")
async def booru(triggerMessage):
    await client.send_typing(triggerMessage.channel)
    global SUPPORTED
    tokens = triggerMessage.content.split()
    #if (len(tokens) == 1 or tokens[1].lower() == "help"):
    if (len(tokens) <= 2):
        await client.send_message(triggerMessage.channel, "Syntax is `!booru booru_name tag0 ...`\nCurrently supported boorus: " + str(list(functionMap.keys())))
        return

    postRandom(tokens[1], tokens[2:])

    return

@commands.registerEventHandler(name="unbusy")
async def unbusy(triggerMessage):
    global busy
    busy = False

class BooruGame:
    def __init__(self, tags):
        self.userScores = {}
        self.tags = tags
        self.previousGuesses = []
        self.timeRemaining = 15 + 3 * len(tags)

    def wasguessed(self, guess):
        return guess in self.previousGuesses

    def guess(self, guess, user):
        guess = guess.replace("`", "").casefold()
        if user not in self.userScores:
            self.userScores[user] = 0

        if guess in self.previousGuesses:
            self.timeRemaining -= 1
            return "`" + guess + "` was already guessed."

        if guess in self.tags:
            self.userScores[user] += 1
            self.previousGuesses.append(guess)
            self.tags.remove(guess)
            self.timeRemaining += 5
            return "`" + guess + "`: Correct! " + str(1) + " points. " + str(len(self.tags)) + " tags left."
        else:
            return "`" + guess + "`: Nope!"

gameInstances = {}

winnerTally = {}

try:
    with open("booruwins.json", "r") as winfile:
        winnerTally = json.loads(winfile.read())
except FileNotFoundError:
    with open("booruwins.json", "w") as winfile:
        winfile.write(json.dumps({}))

def nameFromId(channel, id):
    member = discord.utils.find(lambda m: m.id == scores[0][0], channel.server.members)
    name = member.name
    if member.nick is not None:
        name = member.nick
    return name

async def endGame(channel):
    global gameInstances

    game = gameInstances[channel]
    del gameInstances[channel]

    endMsg = "Game Complete!\n" + "Unguessed tags were: `" + str(game.tags)+"`\n" + "Guessed tags were: `" + str(game.previousGuesses) + "`"
    await client.send_message(channel, endMsg)
    scoreDict = game.userScores
    scores = [(k, scoreDict[k]) for k in sorted(scoreDict, key=scoreDict.get, reverse=True)]
    if len(scores) > 0:
        scoreString = ""
        for id, score in scores:
            name = nameFromId(channel, id)
            scoreString += "User " + str(name) + " scored " + str(score) + "\n"

        name = nameFromId(channel, scores[0][0])
        await client.send_message(channel, scoreString + "\n" + str(name) + " wins!")

        if len(scores) > 2:
            if member.id not in winnerTally:
                winnerTally[member.id] = 0

            winnerTally[member.id] += 1
            with open("booruwins.json", "w") as winfile:
                winfile.write(json.dumps(winnerTally, indent=4))

@commands.registerEventHandler(triggerType="\\timeTick", name="boorugametick")
async def updateTime():
    global gameInstances
    gamesToStop = []
    for c in gameInstances:
        gameInstances[c].timeRemaining -= 1
        if gameInstances[c].timeRemaining <= 0:
            gamesToStop.append(c)
            print("Stopping " + str(c))
    for c in gamesToStop:
        await client.send_message(c, "Timed out!")
        await endGame(c)

def lookup_tag(tag):
    global headers
    session = requests.Session()
    session.headers.update(headers)
    response = session.get("http://e621.net/tag/index.json?name=" + tag)
    j = response.json()
    if (len(j) == 0):
        return 0
    else:
        return j[0]["count"]

@commands.registerEventHandler(name="bgs")
@commands.registerEventHandler(name="boorugamestart")
async def startBooruGame(triggerMessage):
    if triggerMessage.channel in gameInstances:
        await client.send_message(triggerMessage.channel, "A game is already in progress in this channel")
    else:
        tags = []
        try:
            j = getData("http://e621.net/post/index.json?limit={0}&tags={1}", ["order:random","-cub"])

            target = None
            for i in j: # look for a suitable post
                file_ext = i["file_ext"]
                if i["file_ext"] not in ['png', 'jpg']:
                    continue
                if len(i["tags"]) <= 5:
                    continue
                target = i
                break

            if target is None:
                await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. Couldn't find a suitable post. Try again?")
                return

            tags = target["tags"].casefold().split(" ")
            target = target["file_url"]

            filename = downloadImage(target)

            with open(filename, "rb") as image:
                await client.send_file(triggerMessage.channel, image, filename=filename)

        except JSONDecodeError:
            await client.send_message(triggerMessage.channel, "Oopsie Woopsie. Failed to decode json.")
            return
        except Exception as e:
            await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. One of many possible disasters has occured. Try `!booru help`\nException: " + type(e).__name__)
            print(e) #hopefully this does something useful
            return

        print(tags)

        gameInstances[triggerMessage.channel] = BooruGame(tags)
        await client.send_message(triggerMessage.channel, "Game started. The post has " + str(len(tags)) + " tags to guess.")

@commands.registerEventHandler(name="boorugamestop")
@commands.registerEventHandler(name="boorugamequit")
@commands.registerEventHandler(name="bgq")
async def endBooruGame(triggerMessage):
    await endGame(triggerMessage.channel)

@commands.registerEventHandler(name="bg")
@commands.registerEventHandler(name="booruguess")
async def booruGameGuess(triggerMessage):
    args = triggerMessage.content.split()
    for arg in args[1:]:
        await client.send_message(triggerMessage.channel, gameInstances[triggerMessage.channel].guess(str(arg), triggerMessage.author.id))

    if len(gameInstances[triggerMessage.channel].tags) == 0:
        await endBooruGame(triggerMessage)

@commands.registerEventHandler(name="booruleaders")
async def booruLeaders(triggerMessage):
    ids = [x.id for x in triggerMessage.server.members]
    leaderboard = { k: winnerTally[k] for k in winnerTally if k in ids}

    leaderboardString = ""
    for id in leaderboard:
        name = nameFromId(triggerMessage.channel, id)
        leaderboardString += name + " : " + str(leaderboard[id]) + "\n"
    await client.send_message(triggerMessage.channel, leaderboardString)<commit_msg>rule34 support<commit_after>import commands
import permissions
import discord
import requests
import random
import time
import json
from xml.dom import minidom

from json import JSONDecodeError

client = None

def getResponse(endpoint, tags, limit=10):
    headers = {"user-agent":"[^_^]/1.0"}
    t = tags.pop(0)
    for x in tags:
        t+="+"+x
    session = requests.Session()
    session.headers.update(headers)

    response = session.get(endpoint.format(limit, t))
    return response

def getData(endpoint, tags, limit=10): # json
    response = getResponse(endpoint, tags, limit=10)
    j = response.json()
    return j

def getDOM(endpoint, tags, limit=10)
    response = getResponse(endpoint, tags, limit=10)
    return minidom.parseString(response.text)

def downloadImage(url):
    headers = {"user-agent":"[^_^]/1.0"}
    session = requests.Session()
    session.headers.update(headers)

    file_response = session.get(url)
    file_extension = url[url.rfind(".")+1:]

    #https://stackoverflow.com/a/39217788
    data = file_response.content
    print(len(data))
    with open("out."+file_extension, 'wb') as f:
        f.write(data)
        f.close()

    return "out."+file_extension

def gelbooru(tags):
    j = getData("http://gelbooru.com/index.php?page=dapi&limit={0}&s=post&&q=index&json=1&tags={1}", tags)

    target = j[random.randint(0, len(j))]['file_url']
    return downloadImage(target)

#tags should be a list of desired tags
def e621(tags):
    j = getData("http://e621.net/post/index.json?limit={0}&tags={1}", tags)

    target = j[random.randint(0, len(j))]['file_url']
    return downloadImage(target)

def rule34(tags):
    dom = getDom("https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit={0}&tags={1}", tags)
    posts = dom.getElementsByTagName("post")

    target = posts[random.randint(0, len(posts))].getAttribute('file_url')
    return downloadImage(target)

functionMap = {"e621":e621, "gelbooru":gelbooru, "rule34":rule34}

async def postRandom(booru, tags):
    try:
        out = functionMap[booru](tags)

        with open(out, "rb") as image:
            await client.send_file(triggerMessage.channel, image, filename=out)
    except IndexError:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. " + tokens[1] + " returned no search results.")
    except KeyError:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie. " + tokens[1] + " is not supported.\nSupported boorus: "+str(SUPPORTED))
    except JSONDecodeError:
        await client.send_message(triggerMessage.channel, "Oopsie Woopsie. Failed to decode json. " + tokens[1] + " returned an empty response, or something weird")
    except Exception as e:
        await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. One of many possible disasters has occured. Try `!booru help`\nException: " + type(e).__name__)
        print(e) #hopefully this does something useful

@commands.registerEventHandler(name="booru", exclusivity="global")
async def booru(triggerMessage):
    await client.send_typing(triggerMessage.channel)
    global SUPPORTED
    tokens = triggerMessage.content.split()
    #if (len(tokens) == 1 or tokens[1].lower() == "help"):
    if (len(tokens) <= 2):
        await client.send_message(triggerMessage.channel, "Syntax is `!booru booru_name tag0 ...`\nCurrently supported boorus: " + str(list(functionMap.keys())))
        return

    postRandom(tokens[1], tokens[2:])

    return

@commands.registerEventHandler(name="unbusy")
async def unbusy(triggerMessage):
    global busy
    busy = False

class BooruGame:
    def __init__(self, tags):
        self.userScores = {}
        self.tags = tags
        self.previousGuesses = []
        self.timeRemaining = 15 + 3 * len(tags)

    def wasguessed(self, guess):
        return guess in self.previousGuesses

    def guess(self, guess, user):
        guess = guess.replace("`", "").casefold()
        if user not in self.userScores:
            self.userScores[user] = 0

        if guess in self.previousGuesses:
            self.timeRemaining -= 1
            return "`" + guess + "` was already guessed."

        if guess in self.tags:
            self.userScores[user] += 1
            self.previousGuesses.append(guess)
            self.tags.remove(guess)
            self.timeRemaining += 5
            return "`" + guess + "`: Correct! " + str(1) + " points. " + str(len(self.tags)) + " tags left."
        else:
            return "`" + guess + "`: Nope!"

gameInstances = {}

winnerTally = {}

try:
    with open("booruwins.json", "r") as winfile:
        winnerTally = json.loads(winfile.read())
except FileNotFoundError:
    with open("booruwins.json", "w") as winfile:
        winfile.write(json.dumps({}))

def nameFromId(channel, id):
    member = discord.utils.find(lambda m: m.id == scores[0][0], channel.server.members)
    name = member.name
    if member.nick is not None:
        name = member.nick
    return name

async def endGame(channel):
    global gameInstances

    game = gameInstances[channel]
    del gameInstances[channel]

    endMsg = "Game Complete!\n" + "Unguessed tags were: `" + str(game.tags)+"`\n" + "Guessed tags were: `" + str(game.previousGuesses) + "`"
    await client.send_message(channel, endMsg)
    scoreDict = game.userScores
    scores = [(k, scoreDict[k]) for k in sorted(scoreDict, key=scoreDict.get, reverse=True)]
    if len(scores) > 0:
        scoreString = ""
        for id, score in scores:
            name = nameFromId(channel, id)
            scoreString += "User " + str(name) + " scored " + str(score) + "\n"

        name = nameFromId(channel, scores[0][0])
        await client.send_message(channel, scoreString + "\n" + str(name) + " wins!")

        if len(scores) > 2:
            if member.id not in winnerTally:
                winnerTally[member.id] = 0

            winnerTally[member.id] += 1
            with open("booruwins.json", "w") as winfile:
                winfile.write(json.dumps(winnerTally, indent=4))

@commands.registerEventHandler(triggerType="\\timeTick", name="boorugametick")
async def updateTime():
    global gameInstances
    gamesToStop = []
    for c in gameInstances:
        gameInstances[c].timeRemaining -= 1
        if gameInstances[c].timeRemaining <= 0:
            gamesToStop.append(c)
            print("Stopping " + str(c))
    for c in gamesToStop:
        await client.send_message(c, "Timed out!")
        await endGame(c)

def lookup_tag(tag):
    global headers
    session = requests.Session()
    session.headers.update(headers)
    response = session.get("http://e621.net/tag/index.json?name=" + tag)
    j = response.json()
    if (len(j) == 0):
        return 0
    else:
        return j[0]["count"]

@commands.registerEventHandler(name="bgs")
@commands.registerEventHandler(name="boorugamestart")
async def startBooruGame(triggerMessage):
    if triggerMessage.channel in gameInstances:
        await client.send_message(triggerMessage.channel, "A game is already in progress in this channel")
    else:
        tags = []
        try:
            j = getData("http://e621.net/post/index.json?limit={0}&tags={1}", ["order:random","-cub"])

            target = None
            for i in j: # look for a suitable post
                file_ext = i["file_ext"]
                if i["file_ext"] not in ['png', 'jpg']:
                    continue
                if len(i["tags"]) <= 5:
                    continue
                target = i
                break

            if target is None:
                await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. Couldn't find a suitable post. Try again?")
                return

            tags = target["tags"].casefold().split(" ")
            target = target["file_url"]

            filename = downloadImage(target)

            with open(filename, "rb") as image:
                await client.send_file(triggerMessage.channel, image, filename=filename)

        except JSONDecodeError:
            await client.send_message(triggerMessage.channel, "Oopsie Woopsie. Failed to decode json.")
            return
        except Exception as e:
            await client.send_message(triggerMessage.channel, "Oopsie woopsie Uwu. One of many possible disasters has occured. Try `!booru help`\nException: " + type(e).__name__)
            print(e) #hopefully this does something useful
            return

        print(tags)

        gameInstances[triggerMessage.channel] = BooruGame(tags)
        await client.send_message(triggerMessage.channel, "Game started. The post has " + str(len(tags)) + " tags to guess.")

@commands.registerEventHandler(name="boorugamestop")
@commands.registerEventHandler(name="boorugamequit")
@commands.registerEventHandler(name="bgq")
async def endBooruGame(triggerMessage):
    await endGame(triggerMessage.channel)

@commands.registerEventHandler(name="bg")
@commands.registerEventHandler(name="booruguess")
async def booruGameGuess(triggerMessage):
    args = triggerMessage.content.split()
    for arg in args[1:]:
        await client.send_message(triggerMessage.channel, gameInstances[triggerMessage.channel].guess(str(arg), triggerMessage.author.id))

    if len(gameInstances[triggerMessage.channel].tags) == 0:
        await endBooruGame(triggerMessage)

@commands.registerEventHandler(name="booruleaders")
async def booruLeaders(triggerMessage):
    ids = [x.id for x in triggerMessage.server.members]
    leaderboard = { k: winnerTally[k] for k in winnerTally if k in ids}

    leaderboardString = ""
    for id in leaderboard:
        name = nameFromId(triggerMessage.channel, id)
        leaderboardString += name + " : " + str(leaderboard[id]) + "\n"
    await client.send_message(triggerMessage.channel, leaderboardString)
<|endoftext|>