{"action":"created","comment":{"url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/comments/226330589","pull_request_review_id":166118379,"id":226330589,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyNjMzMDU4OQ==","diff_hunk":"@@ -0,0 +1,1098 @@\n+from red_star.plugin_manager import BasePlugin\n+from red_star.command_dispatcher import Command\n+from red_star.rs_errors import CommandSyntaxError, UserPermissionError\n+from red_star.rs_utils import respond, RSArgumentParser, decode_json, split_output\n+from discord import Embed, File, Message, Reaction, Forbidden, NotFound\n+from io import BytesIO\n+from difflib import SequenceMatcher as SM\n+import shlex\n+import json\n+import re\n+from copy import deepcopy\n+from time import time\n+\n+\n+class RoleplayEconomy(BasePlugin):\n+    name = \"roleplay_economy\"\n+    version = \"1.0\"\n+    author = \"GTG3000\"\n+\n+    default_config = {\n+        \"shop_delay\": 120\n+    }\n+\n+    item_base = {\n+        \"name\": \"Default Item\",\n+        \"description\": \"This item looks very generic\",\n+        \"category\": None,\n+        \"image\": None,\n+        \"inshop\": False,\n+        \"p_buy\": 100,\n+        \"p_sell\": 100,\n+        \"fields\": {}\n+    }\n+\n+    char_base = {\n+        \"name\": \"Default Name\",\n+        \"image\": \"\",\n+        \"owner\": 0,\n+        \"money\": 0,\n+        \"inv\": [],\n+        \"inv_key\": [],\n+        \"fields\": {}\n+    }\n+\n+    item_template = {\n+        \"name\": \"default_item\",\n+        \"override\": {},\n+        \"count\": 1\n+    }\n+\n+    shop_template = {\n+        \"user\": 0,          # user ID\n+        \"page\": 0,          # the current page\n+        \"mpages\": 0,        # the max page (pre-calculated because fuck that)\n+        \"category\": \"\",     # the category of items to display\n+        \"time\": 0,          # time.time() to auto-purge the message (the message ID is the key)\n+        \"message\": None     # the message object, for deletions.\n+    }\n+\n+    shops = {}  # self.shops[gid][message_id]: self.Shop\n+\n+    # to lower performance impact of disk IO, move the saving of modified character files to the tick event.\n+    # technically, if the bot is shut down properly, we don't need to save them manually at all - but you never know.\n+    # not doing the same for items because frankly I don't see any reason for items to be modified so often.\n+    _save_chars = False\n+\n+    class Character:\n+        _parent = None\n+        _chars = None\n+        shop = None\n+        gid = None\n+        id = None\n+\n+        _fields = ['owner', 'name', 'image', 'money', 'inv', 'inv_key', 'fields']\n+\n+        def __init__(self, parent, guild: str, character: str):\n+            self._parent = parent\n+            # init the guild, to save up on _initialize calls\n+            self._parent._initchar(guild)\n+            self._parent._inititem(guild)\n+            self.gid = guild\n+            self._chars = self._parent.chars[guild]\n+            self.shop = self._parent.shop_items[guild]\n+            # init the character, for same reasons\n+            _char = character.lower()\n+            self._parent._initbio(self.gid, _char)\n+            self.id = _char\n+\n+        def __getattr__(self, item):\n+            if item in self._fields:\n+                return self._chars[self.id][item]\n+            else:\n+                raise AttributeError\n+\n+        def __setattr__(self, key, value):\n+            if key in self._fields:\n+                self._chars[self.id][key] = value\n+            else:\n+                object.__setattr__(self, key, value)\n+\n+        def get_item(self, query: [int, str], key: bool = False):\n+            \"\"\"\n+            Function to find an item in (key) inventory of the character, by index, name or id\n+            :param query: index, name or id of the item\n+            :param key: whether or not to search in key inventory\n+            :return:\n+            \"\"\"\n+            try:\n+                item = (self.inv_key if key else self.inv)[int(query) - 1]\n+            except ValueError:\n+                try:\n+                    item = [i for i in (self.inv_key if key else self.inv) if i['name'] == query or\n+                            i['override'].get('name', self.shop[i['name']]['name']).lower() == query].pop()\n+                except IndexError:\n+                    raise CommandSyntaxError(f\"No such item: {query}.\")\n+            return item\n+\n+        def stack_item(self, item: dict, key: bool = False):\n+            \"\"\"\n+            Function to add a given item to the (key) inventory of the character.\n+            Ensures same items are stacked, allows taking items through negative counts, adds new items and purges\n+            empty stacks.\n+            :param item: {'name':'', 'override':{}, 'count': 0}\n+            :param key: whether or not to search in key inventory\n+            :return:\n+            \"\"\"\n+            inv = self.inv_key if key else self.inv\n+            for _pos, _item in enumerate(inv):\n+                if item['name'] == _item['name'] and item['override'] == _item['override']:\n+                    inv[_pos]['count'] += item['count']\n+                    if inv[_pos]['count'] <= 0:\n+                        del inv[_pos]\n+                    return\n+            else:\n+                if item['count'] > 0:\n+                    inv.append(item)\n+\n+        def override_item(self, item: dict):\n+            return {**self.shop[item['name']], **item['override'],\n+                    'fields': [(f, v) for f, v in\n+                               list({**dict(self.shop[item['name']]['fields']),\n+                                     **dict(item['override'].get('fields', []))}.items())\n+                               if v]}\n+\n+        def embed(self):\n+            t_embed = Embed(type=\"rich\", colour=16711680)\n+            t_embed.title = self.name\n+            if self.image:\n+                t_embed.set_thumbnail(url=self.image)\n+            for (f_name, f_data) in self.fields:\n+                t_embed.add_field(name=f_name, value=f_data, inline=False)\n+\n+            t_str = \"\"\n+\n+            for item in self.inv_key[:32]:\n+                if item['name'] in self.shop:\n+                    _n = item['override'].get('name')\n+                    t_str += _n[:23] + '*' if _n else self.shop[item['name']]['name'][:24]\n+                else:\n+                    t_str += f\"{item['name'][:24]:-^24}\"\n+                t_str = f\"{t_str:<24}×{item['count']:5d}\\n\" if item['count'] > 1 else f\"{t_str}\\n\"\n+            if len(self.inv_key) > 32:\n+                t_str += f\"And {len(self.inv_key)-32} more...\"\n+\n+            if t_str:\n+                t_embed.add_field(name=\"Key Items\", value=f\"```\\n{t_str}```\")\n+\n+            t_str = \"\"\n+\n+            for item in self.inv[:32]:\n+                if item['name'] in self.shop:\n+                    _n = item['override'].get('name')\n+                    t_str += _n[:23] + '*' if _n else self.shop[item['name']]['name'][:24]\n+                else:\n+                    t_str += f\"{item['name'][:24]:-^24}\"\n+                t_str = f\"{t_str:<24}×{item['count']:5d}\\n\" if item['count'] > 1 else f\"{t_str}\\n\"\n+            if len(self.inv) > 32:\n+                t_str += f\"And {len(self.inv)-32} more...\"\n+\n+            if t_str:\n+                t_embed.add_field(name=\"Items\", value=f\"```\\n{t_str}```\")\n+\n+            t_embed.set_footer(text=f\"Money: {self.money}\")\n+\n+            return t_embed\n+\n+    class Shop:\n+        user: int\n+        page: int\n+        category: str\n+        time: float = 0\n+\n+        _mpage: int\n+        _gid: str\n+        _parent = None\n+        _message: Message\n+\n+        _items: list\n+\n+        _emoji = \"◀🇽▶\"\n+        _len = 10\n+\n+        def __init__(self, parent, user: int, gid: str, category: str=\"\"):\n+            self._parent = parent\n+            self.user = user\n+            self.category = category\n+\n+            self.time = time()\n+            self._gid = gid\n+            self.page = 0\n+\n+            # pregenerate the item text lines. This is an expensive-ish operation and it's better to cache it.\n+\n+            items = [x for x in parent.shop_items[self._gid].values()\n+                     if x['inshop'] and (x['category'] == category or not category)]\n+\n+            if len(items) % 2 == 1:\n+                items.append({'name': \"\", 'p_buy': 0, 'p_sell': 0})\n+\n+            self._items = [\n+                f\"{items[i]['name']:^24}{items[i+1]['name']:^24}\\n\"\n+                f\"[Buy: {items[i]['p_buy']:5d} Sell: {items[i]['p_sell']:5d}]\"\n+                f\"[Buy: {items[i+1]['p_buy']:5d} Sell: {items[i+1]['p_sell']:5d}]\\n\"\n+                for i in range(0, len(items), 2)]\n+\n+\n+            self._mpage = len([x for x in parent.shop_items[self._gid].values()\n+                               if x['inshop'] and (x['category'] == category or not category)]) // self._len\n+\n+        async def post(self, msg):\n+            self._message = await respond(msg, self.text())\n+            await self._message.add_reaction('◀')\n+            await self._message.add_reaction('🇽')\n+            await self._message.add_reaction('▶')\n+            self._parent.shops[self._gid][self._message.id] = self\n+\n+        def text(self):\n+            text = \"```asciidoc\\n\" \\\n+                   f\"{'.🙡Item Shop🙣.':^48}\\n{'='*48}\\n\"\n+            text += (('// ' + self.category + '\\n\\n') if self.category else '\\n')\n+            text += \"\\n\".join(self._items[self.page*self._len:self.page*self._len+self._len])\n+            p = f\"[Page {self.page+1} of {self._mpage}]\"\n+            text += f\"\\n\\n{p:^48}```\"\n+            return text\n+\n+        async def react(self, reaction: Reaction):\n+            if reaction.emoji == '🇽':\n+                del self._parent.shops[self._gid][self._message.id]\n+                await self._message.delete()\n+            elif reaction.emoji == '◀' and self.page > 0:\n+                self.page -= 1\n+                self.time = time()\n+                await self._message.edit(content=self.text())\n+            elif reaction.emoji == '▶' and self.page < self._mpage-1:\n+                self.page += 1\n+                self.time = time()\n+                await self._message.edit(content=self.text())\n+\n+    async def activate(self):\n+        self.bios = self.plugins['roleplay'].bios if 'roleplay' in self.plugins else None\n+        self.chars = self.config_manager.get_plugin_config_file(\"econ_chars.json\",\n+                                                                json_save_args={\"indent\": 2, 'ensure_ascii': False})\n+        self._initchar('default')\n+        self.shop_items = self.config_manager.get_plugin_config_file(\"econ_items.json\",\n+                                                                     json_save_args={\"indent\": 2,\n+                                                                                     'ensure_ascii': False})\n+        self._inititem('default')\n+\n+# Util Functions\n+\n+    def _initchar(self, gid):\n+        if gid not in self.chars:\n+            self.chars[gid] = self.chars.get('default', {'default_char': self.char_base})\n+\n+    def _inititem(self, gid):\n+        if gid not in self.shop_items:\n+            self.shop_items[gid] = self.shop_items.get('default', {'default_item': self.item_base})\n+\n+    def _initbio(self, gid, name):\n+        if name not in self.chars[gid]:\n+            if self.bios and name in self.bios.get(gid, {}):\n+                self.chars[gid][name] = self.chars[gid].get('default_char', self.char_base)\n+                self.chars[gid][name]['name'] = self.bios[gid][name]['name']\n+                self.chars[gid][name]['image'] = self.bios[gid][name]['image']\n+                self.chars[gid][name]['owner'] = self.bios[gid][name]['author']\n+            else:\n+                raise CommandSyntaxError(f\"No such character: {name}\")\n+\n+    @staticmethod\n+    def _generate_item_embed(item: dict, custom=False):\n+        \"\"\"\n+        Helper function to generate item embeds, given (overriden) item data.\n+        The function does not do it's own overriding on account of that requiring it be tied to Character class.\n+        :param item:\n+        :param custom:\n+        :return:\n+        \"\"\"\n+\n+        item_embed = Embed(type=\"rich\", colour=16711680)\n+        item_embed.title = item['name'] + (\" ★\" if custom else \"\")\n+        item_embed.description = f\"{item['description']}\\n\\nPrice: {item['p_buy']}/{item['p_sell']}\"\n+        if item['image']:\n+            item_embed.set_thumbnail(url=item['image'])\n+        for (f_name, f_data) in item[\"fields\"]:\n+            item_embed.add_field(name=f_name, value=f_data, inline=False)\n+\n+        if item['category']:\n+            item_embed.set_footer(text=item['category'])\n+\n+        return item_embed\n+\n+    @staticmethod\n+    def _verify_item(item):\n+\n+        # fields : name, category, description, image, inshop, p_buy, p_sell, fields {}\n+        # just make sure they're at least correct length/type in general.\n+        try:\n+            output = {}\n+            if 'name' in item:\n+                output['name'] = str(item['name'])[:24]\n+            if 'category' in item:\n+                output['category'] = str(item['category'])[:24]\n+            if \"description\" in item:\n+                output[\"description\"] = str(item[\"description\"])[:1000]\n+            if \"image\" in item:\n+                output[\"image\"] = item[\"image\"]\n+            if \"inshop\" in item:\n+                output[\"inshop\"] = bool(item[\"inshop\"])\n+            if \"p_buy\" in item:\n+                try:\n+                    output[\"p_buy\"] = max(0, int(item[\"p_buy\"]))\n+                except ValueError:\n+                    raise CommandSyntaxError(f\"Buying price not a valid integer: {item['p_buy']}.\")\n+            if \"p_sell\" in item:\n+                try:\n+                    output[\"p_sell\"] = max(0, int(item[\"p_sell\"]))\n+                except ValueError:\n+                    raise CommandSyntaxError(f\"Selling price not a valid integer: {item['p_sell']}.\")\n+            if \"fields\" in item:\n+                try:\n+                    output['fields'] = [[str(name)[:32], str(value)[:1024]] for name, value in item['fields']]\n+                except (ValueError, TypeError):\n+                    raise CommandSyntaxError(\"Fields must be lists of two values each.\")\n+            return output\n+        except (KeyError, TypeError):\n+            raise CommandSyntaxError('Item or Override must be a dict.')\n+\n+    def _find_item(self, gid: str, query: str):\n+        \"\"\"\n+        Function to finda possible item using sequence matcher.\n+        Collects possible candidates and returns them too, but don't count on that if it does find a match.\n+        :param gid:\n+        :param query:\n+        :return:\n+        \"\"\"\n+        # no fancy data class this time\n+        _s = self.shop_items[gid]\n+\n+        item = None\n+        possible = []\n+        if query not in _s:\n+            for key, content in _s.items():\n+                match = SM(None, query, content['name'].lower()).ratio()\n+                if match > 0.9:\n+                    item = key\n+                    break\n+                elif match > 0.5:\n+                    possible.append(content['name'])\n+        else:\n+            item = query\n+\n+        return item, possible\n+\n+    def _verify_char(self, char: dict, default: dict):\n+        try:\n+            # char_base = {\n+            #     \"name\": \"Default Name\",\n+            #     \"image\": \"\",\n+            #     \"owner\": 0,\n+            #     \"money\": 0,\n+            #     \"inv\": [],\n+            #     \"inv_key\": [],\n+            #     \"fields\": []\n+            # }\n+\n+            output = dict()\n+            output['name'] = char.get('name', default['name'])[:32]\n+            output['image'] = char.get('image', default['image'])\n+            output['owner'] = int(char.get('owner', default['owner']))\n+            output['money'] = max(0, int(char.get('money', default['money'])))\n+\n+            # All items have to be at *least* valid in their format.\n+            # We don't strip items that have invalid base item ids, but we do strip ones that have negative counts.\n+            if 'inv' in char:\n+                output['inv'] = [\n+                    {'name': str(i['name']), 'override': self._verify_item(i['override']), 'count': int(i['count'])}\n+                    for i in char['inv'] if int(i['count']) > 0\n+                ]\n+            else:\n+                output['inv'] = deepcopy(default['inv'])\n+            if 'inv_key' in char:\n+                output['inv_key'] = [\n+                    {'name': str(i['name']), 'override': self._verify_item(i['override']), 'count': int(i['count'])}\n+                    for i in char['inv_key'] if int(i['count']) > 0\n+                ]\n+            else:\n+                output['inv_key'] = deepcopy(default['inv_key'])\n+            if 'fields' in char:\n+                try:\n+                    output['fields'] = [[str(name)[:32], str(value)[:1024]] for name, value in char['fields']]\n+                except (ValueError, TypeError):\n+                    raise CommandSyntaxError(\"Fields must be lists of two string values each.\")\n+            else:\n+                output['fields'] = deepcopy(default['fields'])\n+\n+            return output\n+\n+        except (KeyError, TypeError):\n+            raise CommandSyntaxError('Character must be a dict.')\n+\n+# Commands\n+\n+    @Command('Char',\n+             doc=\"Prints information about characters and their inventories,\"\n+                 \" down to specific items, and allows item transfer.\\n\"\n+                 \"When called with no flags, prings a character sheet embed.\\n\\n\"\n+                 \"-[-i]tem/-[-k]eyitem: prints information about an item from either inventory, useful to see \"\n+                 \"information about custom items.\\n\"\n+                 \"  -[-d]ump: dumps the selected item information, for backup purposes.\\n\"\n+                 \"  -[-g]ive: transfers the item to another character, with optional amount.\\n\"\n+                 \"  -[-s]ell: sells the selected item, with optional amount.\\n\"\n+                 \"-[-d]ump: when used without -i flag, dumps the character information, for backup purposes.\\n\"\n+                 \"-[-p]ay : transfers money to a specified character, with a specified amount.\\n\"\n+                 \"-[-b]uy : purchases specified item from the shop, with optional amount.\",\n+             syntax=\"(name) [-[-i]tem/-[-k]eyitem (pos/item name) [-[-d]ump] [-[-g]ive recipient [amount]] \"\n+                    \"[-[-s]ell [amount]]] [-[-d]ump] [-[-p]ay (recipient) (amount)] [-[-b]uy (item) [amount]]\",\n+             category=\"role_play_economy\")\n+    async def _char(self, msg):\n+        gid = str(msg.guild.id)\n+\n+        parser = RSArgumentParser()\n+        parser.add_argument(\"command\")\n+        parser.add_argument(\"char\", nargs='+', default=[])\n+        item_args = parser.add_mutually_exclusive_group()\n+        item_args.add_argument('-i', '--item', nargs='+', default=[])\n+        item_args.add_argument('-k', '--keyitem', nargs='+', default=[])\n+        item_manip = parser.add_mutually_exclusive_group()\n+        item_manip.add_argument('-g', '--give', nargs='+')\n+        item_manip.add_argument('-s', '--sell', nargs='?', const='1')\n+        parser.add_argument('-d', '--dump', action='store_true')\n+        parser.add_argument('-p', '--pay', nargs=2)\n+        parser.add_argument('-b', '--buy', nargs='+')\n+\n+        try:\n+            args = parser.parse_args(shlex.split(msg.clean_content))\n+        except ValueError as e:\n+            self.logger.warning(f\"Unable to split {msg.clean_content}. {e}\")\n+            raise CommandSyntaxError(e)\n+\n+        args['char'] = ' '.join(args['char']).lower()\n+        args['item'] = ' '.join(args['item']).lower()\n+        args['keyitem'] = ' '.join(args['keyitem']).lower()\n+\n+        # creating a Character instance automatically inits all relevant data.\n+        # Raises KeyError if character doesn't exist and can't be created automatically from bios\n+        char = self.Character(self, gid, args['char'])\n+\n+        if args['item'] or args['keyitem']:\n+            # enter the depths of item manipulation.\n+            # key inventory takes preference, but otherwise the items are treated about same.\n+            item = char.get_item(args['keyitem'] or args['item'], bool(args['keyitem']))\n+\n+            if args['dump']:\n+                # char (name) -i/k (item) -d\n+                # -d is a multi-purpose flag, and with -i/k it's used to create backups of custom items.\n+                # or you know, grabbing them to edit.\n+                async with msg.channel.typing():\n+                    await respond(msg, \"**AFFIRMATIVE. File upload completed.**\",\n+                                  file=File(BytesIO(bytes(json.dumps(item, indent=2, ensure_ascii=False),\n+                                                          encoding=\"utf8\")),\n+                                            filename=item['name']+'.json'))\n+            elif args['give']:\n+                # char (name) -i/k (item) -g (name) [amount]\n+                # attempts to give an item to another character. We assume that char ids are one word long.\n+                # or they can be wrapped in quotes with shlex.\n+                if args['keyitem']:\n+                    raise UserPermissionError('Can not give away key items.')\n+\n+                recipient = self.Character(self, gid, args['give'][0].lower())\n+                try:\n+                    amount = int(args['give'][1]) if len(args['give']) > 1 else 1\n+                except ValueError:\n+                    raise CommandSyntaxError(f\"{args['give'][1]} is not a valid amount.\")\n+\n+                if amount > item['count']:\n+                    raise UserPermissionError(\"Can not give more items than you possess.\")\n+                elif amount < 1:\n+                    raise UserPermissionError(\"Can not take items this way.\")\n+\n+                recipient.stack_item({**item, \"count\": amount})\n+                char.stack_item({**item, \"count\": -amount})\n+                self._save_chars = True\n+\n+                item_string = f\"{amount} {char.override_item(item)['name']}s\" if amount > 1 else \\\n+                    char.override_item(item)['name']\n+\n+                await respond(msg, f\"**AFFIRMATIVE. {item_string} given to {recipient.name}.**\")\n+            elif args['sell']:\n+                # char (name) -i/k (item) -s [amount]\n+                # exactly what it says on the tin - determine prices, determine amount, prevent selling items with\n+                # no price or that you don't have.\n+                _item = char.override_item(item)\n+                price = _item['p_sell']\n+                name = _item['name']\n+                if args['keyitem']:\n+                    raise UserPermissionError('Can not sell key items.')\n+\n+                if price <= 0:\n+                    await respond(msg, \"**WARNING: Failed to find buyer for your item.**\")\n+                    return\n+\n+                try:\n+                    amount = int(args['sell'])\n+                except ValueError:\n+                    raise CommandSyntaxError(f\"{args['sell']} is not a valid amount.\")\n+\n+                if amount > item['count']:\n+                    raise UserPermissionError(\"Can not sell more items than you possess.\")\n+                elif amount < 1:\n+                    raise UserPermissionError(\"Can not buy items this way.\")\n+\n+                if item['override']:\n+                    self.logger.info(f\"Overriden item sold:\\n\\n{json.dumps(item)}\")\n+                char.stack_item({**item, \"count\": -amount})\n+                char.money += price * amount\n+                self._save_chars = True\n+\n+                await respond(msg, f\"**AFFIRMATIVE. {amount} {name}{'s' if amount>1 else ''} \"\n+                                   f\"sold for {price*amount} currency.**\")\n+            else:\n+                await respond(msg, embed=self._generate_item_embed(char.override_item(item), bool(item['override'])))\n+        elif args['dump']:\n+            # the second part of the multi-purpose -d flag. Dumps character for backup/editing.\n+            async with msg.channel.typing():\n+                await respond(msg, \"**AFFIRMATIVE. File upload completed.**\",\n+                              file=File(BytesIO(bytes(json.dumps(char._chars[char.id], indent=2, ensure_ascii=False),\n+                                                      encoding=\"utf8\")),\n+                                        filename=char.id+'.json'))\n+        elif args['pay']:\n+            # char (name) -p (name) (amount)\n+            # transfers money between characters, pretty straightforward.\n+            try:\n+                amount = int(args['pay'][1])\n+                if not 0 < amount < char.money:\n+                    raise ValueError\n+            except ValueError:\n+                raise CommandSyntaxError(f\"{args['pay'][1]} is not a valid amount.\")\n+\n+            recipient = self.Character(self, gid, args['pay'][0].lower())\n+            recipient.money += amount\n+            char.money -= amount\n+            self._save_chars = True\n+            await respond(msg, f\"**AFFIRMATIVE. {amount} currency transferred to {recipient.name}.**\")\n+        elif args['buy']:\n+            # char (name) -b (item) [amount]\n+            # attempts to buy a number of items from the shop\n+\n+            try:\n+                amount = max(1, int(args['buy'][1]))\n+            except IndexError:\n+                amount = 1\n+            except ValueError:\n+                raise CommandSyntaxError(f\"{args['buy'][1]} is not a valid amount.\")\n+\n+            item, _ = self._find_item(gid, args['buy'][0])\n+\n+            if item and char.shop[item]['inshop'] and char.shop[item]['p_buy'] > 0:\n+                if char.money > amount * char.shop[item]['p_buy']:\n+                    char.stack_item({'name': item, 'override': {}, 'count': amount})\n+                    char.money -= amount * char.shop[item]['p_buy']\n+                    self._save_chars = True\n+                    await respond(msg, f\"**AFFIRMATIVE. {amount} {char.shop[item]['name']} purchased.**\")\n+                else:\n+                    await respond(msg, \"**WARNING: Insufficient funds.**\")\n+            else:\n+                await respond(msg, \"**NEGATIVE. No such item.**\")\n+        elif args['sell']:\n+            # char (name) -s (item/pos) [amount]\n+            # in case you forget that -i (item) -s [amount] is the syntax.\n+\n+            item = char.get_item(args['sell'][0])\n+            _item = char.override_item(item)\n+            price = _item['p_sell']\n+            name = _item['name']\n+\n+            if price <= 0:\n+                await respond(msg, \"**WARNING: Failed to find buyer for your item.**\")\n+                return\n+\n+            try:\n+                amount = max(1, int(args['sell'][1]))\n+            except IndexError:\n+                amount = 1\n+            except ValueError:\n+                raise CommandSyntaxError(f\"{args['buy'][1]} is not a valid amount.\")\n+\n+            if amount > item['count']:\n+                raise UserPermissionError(\"Can not sell more items than you possess.\")\n+            elif amount < 1:\n+                raise UserPermissionError(\"Can not buy items this way.\")\n+\n+            if item['override']:\n+                self.logger.info(f\"Overriden item sold:\\n\\n{json.dumps(item)}\")\n+\n+            char.stack_item({**item, \"count\": -amount})\n+            char.money += price * amount\n+            self._save_chars = True\n+\n+            await respond(msg, f\"**AFFIRMATIVE. {amount} {name}{'s' if amount>1 else ''} \"\n+                               f\"sold for {price*amount} currency.**\")\n+        else:\n+            await respond(msg, embed=char.embed())\n+\n+    @Command('Give', 'GiveKey',\n+             doc=\"Gives the specified character a specified item. You can specify item by it's ID or name.\\n\"\n+                 \"Use \\\"GiveKey\\\" alias to add items to the characters key inventory.\\n\"\n+                 \"Use \\\"money\\\" item to give characters currency.\",\n+             syntax=\"(name) (item) [amount]\",\n+             perms={\"manage_messages\"},\n+             category=\"role_play_economy\")\n+    async def _give(self, msg):\n+        gid = str(msg.guild.id)\n+        parser = RSArgumentParser()\n+        parser.add_argument('command')\n+        parser.add_argument('name')\n+        parser.add_argument('item')\n+        parser.add_argument('amount', nargs='?', default=1, type=int)\n+\n+        try:\n+            args = parser.parse_args(shlex.split(msg.clean_content))\n+        except ValueError as e:\n+            self.logger.warning(f\"Unable to split {msg.clean_content}. {e}\")\n+            raise CommandSyntaxError(e)\n+\n+        if args['amount'] < 1:\n+            raise CommandSyntaxError(\"Please use the Take command to remove items.\")\n+\n+        char = self.Character(self, gid, args['name'])\n+\n+        query = args['item'].lower()\n+\n+        if query == 'money':\n+            char.money += args['amount']\n+            self._save_chars = True\n+            await respond(msg, f\"**AFFIRMATIVE. {args['amount']} currency given to {char.name}.**\")\n+        else:\n+            item, possible = self._find_item(gid, query)\n+            if item:\n+                char.stack_item({\"name\": query, \"override\": {}, \"count\": args['amount']},\n+                                args['command'].endswith('key'))\n+                self._save_chars = True\n+                await respond(msg, f\"**AFFIRMATIVE. {args['amount']} \"\n+                                   f\"{char._items[query]['name']} given to {char.name}.**\")\n+            elif possible:\n+                await split_output(msg,  \"**ANALYSIS: Perhaps you meant one of these items?**\", possible)\n+            else:\n+                await respond(msg, f\"**WARNING: Could not find item: {query}.**\")\n+\n+    @Command('Take', 'TakeKey',\n+             doc=\"Takes specified items from a specified character. Items can be specified by ID, name or position \"\n+                 \"in the inventory.\\n\"\n+                 \"Use \\\"TakeKey\\\" alias to take items from the characters key inventory.\"\n+                 \"Use \\\"money\\\" item to take characters currency.\",\n+             syntax=\"(name) (item) [amount]\",\n+             perms={\"manage_messages\"},\n+             category=\"role_play_economy\")\n+    async def _take(self, msg):\n+        gid = str(msg.guild.id)\n+\n+        parser = RSArgumentParser()\n+        parser.add_argument('command')\n+        parser.add_argument('name')\n+        parser.add_argument('item')\n+        parser.add_argument('amount', nargs='?', default=1, type=int)\n+\n+        try:\n+            args = parser.parse_args(shlex.split(msg.clean_content))\n+        except ValueError as e:\n+            self.logger.warning(f\"Unable to split {msg.clean_content}. {e}\")\n+            raise CommandSyntaxError(e)\n+\n+        if args['amount'] < 1:\n+            raise CommandSyntaxError(\"Please use the Give command to add items.\")\n+\n+        char = self.Character(self, gid, args['name'])","path":"roleplay_economy.py","position":695,"original_position":695,"commit_id":"f11f682eb16f56729c5d816176e5a337272e758f","original_commit_id":"f11f682eb16f56729c5d816176e5a337272e758f","user":{"login":"medeor413","id":15570846,"node_id":"MDQ6VXNlcjE1NTcwODQ2","avatar_url":"https://avatars1.githubusercontent.com/u/15570846?v=4","gravatar_id":"","url":"https://api.github.com/users/medeor413","html_url":"https://github.com/medeor413","followers_url":"https://api.github.com/users/medeor413/followers","following_url":"https://api.github.com/users/medeor413/following{/other_user}","gists_url":"https://api.github.com/users/medeor413/gists{/gist_id}","starred_url":"https://api.github.com/users/medeor413/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/medeor413/subscriptions","organizations_url":"https://api.github.com/users/medeor413/orgs","repos_url":"https://api.github.com/users/medeor413/repos","events_url":"https://api.github.com/users/medeor413/events{/privacy}","received_events_url":"https://api.github.com/users/medeor413/received_events","type":"User","site_admin":false},"body":"^","created_at":"2018-10-18T14:34:30Z","updated_at":"2018-10-18T14:37:42Z","html_url":"https://github.com/medeor413/Red_Star_Plugins/pull/5#discussion_r226330589","pull_request_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/comments/226330589"},"html":{"href":"https://github.com/medeor413/Red_Star_Plugins/pull/5#discussion_r226330589"},"pull_request":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5"}}},"pull_request":{"url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5","id":222445049,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIyNDQ1MDQ5","html_url":"https://github.com/medeor413/Red_Star_Plugins/pull/5","diff_url":"https://github.com/medeor413/Red_Star_Plugins/pull/5.diff","patch_url":"https://github.com/medeor413/Red_Star_Plugins/pull/5.patch","issue_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/5","number":5,"state":"open","locked":false,"title":"Implemented first (proper) version or roleplay economy plugin.","user":{"login":"GTG3000","id":1013083,"node_id":"MDQ6VXNlcjEwMTMwODM=","avatar_url":"https://avatars2.githubusercontent.com/u/1013083?v=4","gravatar_id":"","url":"https://api.github.com/users/GTG3000","html_url":"https://github.com/GTG3000","followers_url":"https://api.github.com/users/GTG3000/followers","following_url":"https://api.github.com/users/GTG3000/following{/other_user}","gists_url":"https://api.github.com/users/GTG3000/gists{/gist_id}","starred_url":"https://api.github.com/users/GTG3000/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GTG3000/subscriptions","organizations_url":"https://api.github.com/users/GTG3000/orgs","repos_url":"https://api.github.com/users/GTG3000/repos","events_url":"https://api.github.com/users/GTG3000/events{/privacy}","received_events_url":"https://api.github.com/users/GTG3000/received_events","type":"User","site_admin":false},"body":"Features:\r\n- Keeps track of character sheets, including:\r\n  - Fully customiseable fields, meant to store misc data\r\n  - Character inventories (including a \"key\" inventory that characters can not edit on their own)\r\n  - Money\r\n  - A single image per character\r\n  - Owner of the character may transfer and sell items at whim.\r\n  - Integration with roleplay plugin allows for easy creation of characters to be filled out.\r\n- An item shop, enabling characters to purchase/sell items.\r\n  - Support for fully customizeable unique items to be given to characters, without cluttering up the shop.\r\n\r\nCommands:\r\n\r\nUser commands:\r\n- Char\r\n- Shop\r\n- ShopCategories\r\n- ShopItems\r\n- ItemInfo\r\n- DumpItem\r\n\r\nModerator commands:\r\n- ListChars\r\n- ListItems\r\n- Give\r\n- Take\r\n- GiveCustom\r\n- UploadChar\r\n- UploadItem\r\n- DeleteChar\r\n- DeleteItem\r\n\r\nMaintainer commands:\r\n- ReloadEconChars\r\n- ReloadEconItems","created_at":"2018-10-12T12:08:00Z","updated_at":"2018-10-18T14:37:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5cee764ae9faf8946b46abf4ba6062f11d22897f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5/commits","review_comments_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5/comments","review_comment_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/comments{/number}","comments_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/5/comments","statuses_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/statuses/f11f682eb16f56729c5d816176e5a337272e758f","head":{"label":"GTG3000:master","ref":"master","sha":"f11f682eb16f56729c5d816176e5a337272e758f","user":{"login":"GTG3000","id":1013083,"node_id":"MDQ6VXNlcjEwMTMwODM=","avatar_url":"https://avatars2.githubusercontent.com/u/1013083?v=4","gravatar_id":"","url":"https://api.github.com/users/GTG3000","html_url":"https://github.com/GTG3000","followers_url":"https://api.github.com/users/GTG3000/followers","following_url":"https://api.github.com/users/GTG3000/following{/other_user}","gists_url":"https://api.github.com/users/GTG3000/gists{/gist_id}","starred_url":"https://api.github.com/users/GTG3000/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GTG3000/subscriptions","organizations_url":"https://api.github.com/users/GTG3000/orgs","repos_url":"https://api.github.com/users/GTG3000/repos","events_url":"https://api.github.com/users/GTG3000/events{/privacy}","received_events_url":"https://api.github.com/users/GTG3000/received_events","type":"User","site_admin":false},"repo":{"id":149472216,"node_id":"MDEwOlJlcG9zaXRvcnkxNDk0NzIyMTY=","name":"Red_Star_Plugins","full_name":"GTG3000/Red_Star_Plugins","private":false,"owner":{"login":"GTG3000","id":1013083,"node_id":"MDQ6VXNlcjEwMTMwODM=","avatar_url":"https://avatars2.githubusercontent.com/u/1013083?v=4","gravatar_id":"","url":"https://api.github.com/users/GTG3000","html_url":"https://github.com/GTG3000","followers_url":"https://api.github.com/users/GTG3000/followers","following_url":"https://api.github.com/users/GTG3000/following{/other_user}","gists_url":"https://api.github.com/users/GTG3000/gists{/gist_id}","starred_url":"https://api.github.com/users/GTG3000/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GTG3000/subscriptions","organizations_url":"https://api.github.com/users/GTG3000/orgs","repos_url":"https://api.github.com/users/GTG3000/repos","events_url":"https://api.github.com/users/GTG3000/events{/privacy}","received_events_url":"https://api.github.com/users/GTG3000/received_events","type":"User","site_admin":false},"html_url":"https://github.com/GTG3000/Red_Star_Plugins","description":"Extra plugins for https://github.com/medeor413/Red_Star","fork":true,"url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins","forks_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/forks","keys_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/teams","hooks_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/hooks","issue_events_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/issues/events{/number}","events_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/events","assignees_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/assignees{/user}","branches_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/branches{/branch}","tags_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/tags","blobs_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/git/refs{/sha}","trees_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/statuses/{sha}","languages_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/languages","stargazers_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/stargazers","contributors_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/contributors","subscribers_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/subscribers","subscription_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/subscription","commits_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/commits{/sha}","git_commits_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/git/commits{/sha}","comments_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/comments{/number}","issue_comment_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/issues/comments{/number}","contents_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/contents/{+path}","compare_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/merges","archive_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/downloads","issues_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/issues{/number}","pulls_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/pulls{/number}","milestones_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/milestones{/number}","notifications_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/labels{/name}","releases_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/releases{/id}","deployments_url":"https://api.github.com/repos/GTG3000/Red_Star_Plugins/deployments","created_at":"2018-09-19T15:28:08Z","updated_at":"2018-10-12T12:05:59Z","pushed_at":"2018-10-12T12:05:58Z","git_url":"git://github.com/GTG3000/Red_Star_Plugins.git","ssh_url":"git@github.com:GTG3000/Red_Star_Plugins.git","clone_url":"https://github.com/GTG3000/Red_Star_Plugins.git","svn_url":"https://github.com/GTG3000/Red_Star_Plugins","homepage":null,"size":41,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"medeor413:master","ref":"master","sha":"043ae0e67a348306a49485fb3c61d3c194e06a67","user":{"login":"medeor413","id":15570846,"node_id":"MDQ6VXNlcjE1NTcwODQ2","avatar_url":"https://avatars1.githubusercontent.com/u/15570846?v=4","gravatar_id":"","url":"https://api.github.com/users/medeor413","html_url":"https://github.com/medeor413","followers_url":"https://api.github.com/users/medeor413/followers","following_url":"https://api.github.com/users/medeor413/following{/other_user}","gists_url":"https://api.github.com/users/medeor413/gists{/gist_id}","starred_url":"https://api.github.com/users/medeor413/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/medeor413/subscriptions","organizations_url":"https://api.github.com/users/medeor413/orgs","repos_url":"https://api.github.com/users/medeor413/repos","events_url":"https://api.github.com/users/medeor413/events{/privacy}","received_events_url":"https://api.github.com/users/medeor413/received_events","type":"User","site_admin":false},"repo":{"id":149367844,"node_id":"MDEwOlJlcG9zaXRvcnkxNDkzNjc4NDQ=","name":"Red_Star_Plugins","full_name":"medeor413/Red_Star_Plugins","private":false,"owner":{"login":"medeor413","id":15570846,"node_id":"MDQ6VXNlcjE1NTcwODQ2","avatar_url":"https://avatars1.githubusercontent.com/u/15570846?v=4","gravatar_id":"","url":"https://api.github.com/users/medeor413","html_url":"https://github.com/medeor413","followers_url":"https://api.github.com/users/medeor413/followers","following_url":"https://api.github.com/users/medeor413/following{/other_user}","gists_url":"https://api.github.com/users/medeor413/gists{/gist_id}","starred_url":"https://api.github.com/users/medeor413/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/medeor413/subscriptions","organizations_url":"https://api.github.com/users/medeor413/orgs","repos_url":"https://api.github.com/users/medeor413/repos","events_url":"https://api.github.com/users/medeor413/events{/privacy}","received_events_url":"https://api.github.com/users/medeor413/received_events","type":"User","site_admin":false},"html_url":"https://github.com/medeor413/Red_Star_Plugins","description":"Extra plugins for https://github.com/medeor413/Red_Star","fork":false,"url":"https://api.github.com/repos/medeor413/Red_Star_Plugins","forks_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/forks","keys_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/keys{/key_id}","collaborators_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/teams","hooks_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/hooks","issue_events_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/events{/number}","events_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/events","assignees_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/assignees{/user}","branches_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/branches{/branch}","tags_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/tags","blobs_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/git/refs{/sha}","trees_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/git/trees{/sha}","statuses_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/statuses/{sha}","languages_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/languages","stargazers_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/stargazers","contributors_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/contributors","subscribers_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/subscribers","subscription_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/subscription","commits_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/commits{/sha}","git_commits_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/git/commits{/sha}","comments_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/comments{/number}","issue_comment_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/comments{/number}","contents_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/contents/{+path}","compare_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/compare/{base}...{head}","merges_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/merges","archive_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/downloads","issues_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues{/number}","pulls_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls{/number}","milestones_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/milestones{/number}","notifications_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/labels{/name}","releases_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/releases{/id}","deployments_url":"https://api.github.com/repos/medeor413/Red_Star_Plugins/deployments","created_at":"2018-09-19T00:20:02Z","updated_at":"2018-09-23T19:19:58Z","pushed_at":"2018-10-12T12:08:01Z","git_url":"git://github.com/medeor413/Red_Star_Plugins.git","ssh_url":"git@github.com:medeor413/Red_Star_Plugins.git","clone_url":"https://github.com/medeor413/Red_Star_Plugins.git","svn_url":"https://github.com/medeor413/Red_Star_Plugins","homepage":null,"size":31,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":1,"open_issues":4,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5"},"html":{"href":"https://github.com/medeor413/Red_Star_Plugins/pull/5"},"issue":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/5"},"comments":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/issues/5/comments"},"review_comments":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5/comments"},"review_comment":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/pulls/5/commits"},"statuses":{"href":"https://api.github.com/repos/medeor413/Red_Star_Plugins/statuses/f11f682eb16f56729c5d816176e5a337272e758f"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"medeor413"}}