[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n*.idea\n*.DS_Store\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n"
  },
  {
    "path": "AdobeAfterEffects/README.md",
    "content": "## Output\n<img align=\"center\" src='https://github.com/niveshbirangal/thrashrepo/blob/master/discordrpc/aftereffects.png'>\nThey can be customized according to the assets.\n"
  },
  {
    "path": "AdobeAfterEffects/aftereff.py",
    "content": "import rpc\nimport time\nfrom time import mktime\n\nprint(\"Demo for python-discord-rpc\")\nclient_id = '745944488882602035'  # Your application's client ID as a string. (This isn't a real client ID)\nrpc_obj = rpc.DiscordIpcClient.for_platform(client_id)  # Send the client ID to the rpc module\nprint(\"RPC connection successful.\")\n\ntime.sleep(5)\nstart_time = mktime(time.localtime())\nwhile True:\n    activity = {\n            \"state\": \"Adobe\",  # anything you like\n            \"details\": \"Editing\",  # anything you like\n            \"timestamps\": {\n                \"start\": start_time\n            },\n            \"assets\": {\n                \"small_text\": \"Adobe\",  # anything you like\n                \"small_image\": \"small\",  # must match the image key\n                \"large_text\": \"After Effects\",  # anything you like\n                \"large_image\": \"large1\"  # must match the image key\n            }\n        }\n    rpc_obj.set_activity(activity)\n    time.sleep(900)\n"
  },
  {
    "path": "AdobeAfterEffects/rpc.py",
    "content": "# References:\n# * https://github.com/devsnek/discord-rpc/tree/master/src/transports/IPC.js\n# * https://github.com/devsnek/discord-rpc/tree/master/example/main.js\n# * https://github.com/discordapp/discord-rpc/tree/master/documentation/hard-mode.md\n# * https://github.com/discordapp/discord-rpc/tree/master/src\n# * https://discordapp.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields\n\nfrom abc import ABCMeta, abstractmethod\nimport json\nimport logging\nimport os\nimport socket\nimport sys\nimport struct\nimport uuid\n\nOP_HANDSHAKE = 0\nOP_FRAME = 1\nOP_CLOSE = 2\nOP_PING = 3\nOP_PONG = 4\n\nlogger = logging.getLogger(__name__)\n\n\nclass DiscordIpcError(Exception):\n    pass\n\n\nclass DiscordIpcClient(metaclass=ABCMeta):\n\n    \"\"\"Work with an open Discord instance via its JSON IPC for its rich presence API.\n\n    In a blocking way.\n    Classmethod `for_platform`\n    will resolve to one of WinDiscordIpcClient or UnixDiscordIpcClient,\n    depending on the current platform.\n    Supports context handler protocol.\n    \"\"\"\n\n    def __init__(self, client_id):\n        self.client_id = client_id\n        self._connect()\n        self._do_handshake()\n        logger.info(\"connected via ID %s\", client_id)\n\n    @classmethod\n    def for_platform(cls, client_id, platform=sys.platform):\n        if platform == 'win32':\n            return WinDiscordIpcClient(client_id)\n        else:\n            return UnixDiscordIpcClient(client_id)\n\n    @abstractmethod\n    def _connect(self):\n        pass\n\n    def _do_handshake(self):\n        ret_op, ret_data = self.send_recv({'v': 1, 'client_id': self.client_id}, op=OP_HANDSHAKE)\n        # {'cmd': 'DISPATCH', 'data': {'v': 1, 'config': {...}}, 'evt': 'READY', 'nonce': None}\n        if ret_op == OP_FRAME and ret_data['cmd'] == 'DISPATCH' and ret_data['evt'] == 'READY':\n            return\n        else:\n            if ret_op == OP_CLOSE:\n                self.close()\n            raise RuntimeError(ret_data)\n\n    @abstractmethod\n    def _write(self, date: bytes):\n        pass\n\n    @abstractmethod\n    def _recv(self, size: int) -> bytes:\n        pass\n\n    def _recv_header(self) -> (int, int):\n        header = self._recv_exactly(8)\n        return struct.unpack(\"<II\", header)\n\n    def _recv_exactly(self, size) -> bytes:\n        buf = b\"\"\n        size_remaining = size\n        while size_remaining:\n            chunk = self._recv(size_remaining)\n            buf += chunk\n            size_remaining -= len(chunk)\n        return buf\n\n    def close(self):\n        logger.warning(\"closing connection\")\n        try:\n            self.send({}, op=OP_CLOSE)\n        finally:\n            self._close()\n\n    @abstractmethod\n    def _close(self):\n        pass\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *_):\n        self.close()\n\n    def send_recv(self, data, op=OP_FRAME):\n        self.send(data, op)\n        return self.recv()\n\n    def send(self, data, op=OP_FRAME):\n        logger.debug(\"sending %s\", data)\n        data_str = json.dumps(data, separators=(',', ':'))\n        data_bytes = data_str.encode('utf-8')\n        header = struct.pack(\"<II\", op, len(data_bytes))\n        self._write(header)\n        self._write(data_bytes)\n\n    def recv(self) -> (int, \"JSON\"):\n        \"\"\"Receives a packet from discord.\n\n        Returns op code and payload.\n        \"\"\"\n        op, length = self._recv_header()\n        payload = self._recv_exactly(length)\n        data = json.loads(payload.decode('utf-8'))\n        logger.debug(\"received %s\", data)\n        return op, data\n\n    def set_activity(self, act):\n        # act\n        data = {\n            'cmd': 'SET_ACTIVITY',\n            'args': {'pid': os.getpid(),\n                     'activity': act},\n            'nonce': str(uuid.uuid4())\n        }\n        self.send(data)\n\n\nclass WinDiscordIpcClient(DiscordIpcClient):\n\n    _pipe_pattern = R'\\\\?\\pipe\\discord-ipc-{}'\n\n    def _connect(self):\n        for i in range(10):\n            path = self._pipe_pattern.format(i)\n            try:\n                self._f = open(path, \"w+b\")\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n        self.path = path\n\n    def _write(self, data: bytes):\n        self._f.write(data)\n        self._f.flush()\n\n    def _recv(self, size: int) -> bytes:\n        return self._f.read(size)\n\n    def _close(self):\n        self._f.close()\n\n\nclass UnixDiscordIpcClient(DiscordIpcClient):\n\n    def _connect(self):\n        self._sock = socket.socket(socket.AF_UNIX)\n        pipe_pattern = self._get_pipe_pattern()\n\n        for i in range(10):\n            path = pipe_pattern.format(i)\n            if not os.path.exists(path):\n                continue\n            try:\n                self._sock.connect(path)\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n    @staticmethod\n    def _get_pipe_pattern():\n        env_keys = ('XDG_RUNTIME_DIR', 'TMPDIR', 'TMP', 'TEMP')\n        for env_key in env_keys:\n            dir_path = os.environ.get(env_key)\n            if dir_path:\n                break\n        else:\n            dir_path = '/tmp'\n        return os.path.join(dir_path, 'discord-ipc-{}')\n\n    def _write(self, data: bytes):\n        self._sock.sendall(data)\n\n    def _recv(self, size: int) -> bytes:\n        return self._sock.recv(size)\n\n    def _close(self):\n        self._sock.close()\n"
  },
  {
    "path": "AdobeXD/adobexd.py",
    "content": "import rpc\nimport time\nfrom time import mktime\n\nprint(\"Demo for python-discord-rpc\")\nclient_id = '467310358567190559'  # Your application's client ID as a string. (This isn't a real client ID)\nrpc_obj = rpc.DiscordIpcClient.for_platform(client_id)  # Send the client ID to the rpc module\nprint(\"RPC connection successful.\")\n\ntime.sleep(5)\nstart_time = mktime(time.localtime())\nwhile True:\n    activity = {\n            \"state\": \"Adobe\",\n            \"details\": \"Designing\",\n            \"timestamps\": {\n                \"start\": start_time\n            },\n            \"assets\": {\n                \"small_text\": \"Adobe\",\n                \"small_image\": \"adobe\",\n                \"large_text\": \"Illustrator\",\n                \"large_image\": \"xd\"\n            }\n        }\n    rpc_obj.set_activity(activity)\n    time.sleep(900)\n"
  },
  {
    "path": "AdobeXD/rpc.py",
    "content": "# References:\n# * https://github.com/devsnek/discord-rpc/tree/master/src/transports/IPC.js\n# * https://github.com/devsnek/discord-rpc/tree/master/example/main.js\n# * https://github.com/discordapp/discord-rpc/tree/master/documentation/hard-mode.md\n# * https://github.com/discordapp/discord-rpc/tree/master/src\n# * https://discordapp.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields\n\nfrom abc import ABCMeta, abstractmethod\nimport json\nimport logging\nimport os\nimport socket\nimport sys\nimport struct\nimport uuid\n\n\nOP_HANDSHAKE = 0\nOP_FRAME = 1\nOP_CLOSE = 2\nOP_PING = 3\nOP_PONG = 4\n\nlogger = logging.getLogger(__name__)\n\n\nclass DiscordIpcError(Exception):\n    pass\n\n\nclass DiscordIpcClient(metaclass=ABCMeta):\n\n    \"\"\"Work with an open Discord instance via its JSON IPC for its rich presence API.\n\n    In a blocking way.\n    Classmethod `for_platform`\n    will resolve to one of WinDiscordIpcClient or UnixDiscordIpcClient,\n    depending on the current platform.\n    Supports context handler protocol.\n    \"\"\"\n\n    def __init__(self, client_id):\n        self.client_id = client_id\n        self._connect()\n        self._do_handshake()\n        logger.info(\"connected via ID %s\", client_id)\n\n    @classmethod\n    def for_platform(cls, client_id, platform=sys.platform):\n        if platform == 'win32':\n            return WinDiscordIpcClient(client_id)\n        else:\n            return UnixDiscordIpcClient(client_id)\n\n    @abstractmethod\n    def _connect(self):\n        pass\n\n    def _do_handshake(self):\n        ret_op, ret_data = self.send_recv({'v': 1, 'client_id': self.client_id}, op=OP_HANDSHAKE)\n        # {'cmd': 'DISPATCH', 'data': {'v': 1, 'config': {...}}, 'evt': 'READY', 'nonce': None}\n        if ret_op == OP_FRAME and ret_data['cmd'] == 'DISPATCH' and ret_data['evt'] == 'READY':\n            return\n        else:\n            if ret_op == OP_CLOSE:\n                self.close()\n            raise RuntimeError(ret_data)\n\n    @abstractmethod\n    def _write(self, date: bytes):\n        pass\n\n    @abstractmethod\n    def _recv(self, size: int) -> bytes:\n        pass\n\n    def _recv_header(self) -> (int, int):\n        header = self._recv_exactly(8)\n        return struct.unpack(\"<II\", header)\n\n    def _recv_exactly(self, size) -> bytes:\n        buf = b\"\"\n        size_remaining = size\n        while size_remaining:\n            chunk = self._recv(size_remaining)\n            buf += chunk\n            size_remaining -= len(chunk)\n        return buf\n\n    def close(self):\n        logger.warning(\"closing connection\")\n        try:\n            self.send({}, op=OP_CLOSE)\n        finally:\n            self._close()\n\n    @abstractmethod\n    def _close(self):\n        pass\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *_):\n        self.close()\n\n    def send_recv(self, data, op=OP_FRAME):\n        self.send(data, op)\n        return self.recv()\n\n    def send(self, data, op=OP_FRAME):\n        logger.debug(\"sending %s\", data)\n        data_str = json.dumps(data, separators=(',', ':'))\n        data_bytes = data_str.encode('utf-8')\n        header = struct.pack(\"<II\", op, len(data_bytes))\n        self._write(header)\n        self._write(data_bytes)\n\n    def recv(self) -> (int, \"JSON\"):\n        \"\"\"Receives a packet from discord.\n\n        Returns op code and payload.\n        \"\"\"\n        op, length = self._recv_header()\n        payload = self._recv_exactly(length)\n        data = json.loads(payload.decode('utf-8'))\n        logger.debug(\"received %s\", data)\n        return op, data\n\n    def set_activity(self, act):\n        # act\n        data = {\n            'cmd': 'SET_ACTIVITY',\n            'args': {'pid': os.getpid(),\n                     'activity': act},\n            'nonce': str(uuid.uuid4())\n        }\n        self.send(data)\n\n\nclass WinDiscordIpcClient(DiscordIpcClient):\n\n    _pipe_pattern = R'\\\\?\\pipe\\discord-ipc-{}'\n\n    def _connect(self):\n        for i in range(10):\n            path = self._pipe_pattern.format(i)\n            try:\n                self._f = open(path, \"w+b\")\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n        self.path = path\n\n    def _write(self, data: bytes):\n        self._f.write(data)\n        self._f.flush()\n\n    def _recv(self, size: int) -> bytes:\n        return self._f.read(size)\n\n    def _close(self):\n        self._f.close()\n\n\nclass UnixDiscordIpcClient(DiscordIpcClient):\n\n    def _connect(self):\n        self._sock = socket.socket(socket.AF_UNIX)\n        pipe_pattern = self._get_pipe_pattern()\n\n        for i in range(10):\n            path = pipe_pattern.format(i)\n            if not os.path.exists(path):\n                continue\n            try:\n                self._sock.connect(path)\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n    @staticmethod\n    def _get_pipe_pattern():\n        env_keys = ('XDG_RUNTIME_DIR', 'TMPDIR', 'TMP', 'TEMP')\n        for env_key in env_keys:\n            dir_path = os.environ.get(env_key)\n            if dir_path:\n                break\n        else:\n            dir_path = '/tmp'\n        return os.path.join(dir_path, 'discord-ipc-{}')\n\n    def _write(self, data: bytes):\n        self._sock.sendall(data)\n\n    def _recv(self, size: int) -> bytes:\n        return self._sock.recv(size)\n\n    def _close(self):\n        self._sock.close()\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Nivesh Birangal\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">Discord RPC</h1>\n<h5 align=\"center\">\"Discord RPC is a library for interfacing your game with a locally running Discord desktop client\"</h5>\n<!--<img align=\"right\" src='https://github.com/niveshbirangal/discord-rpc/blob/master/readmeassets/intro.gif' width=\"150\">-->\n\n### Status:\n<a href=\"https://img.shields.io/youtube/views/udY540zICDY?style=social\"><img src=\"https://img.shields.io/youtube/views/udY540zICDY?style=social\" alt=\"Stars Badge\"/></a>\n<a href=\"https://github.com/niveshbirangal/discord-rpc/stargazers\"><img src=\"https://img.shields.io/github/stars/niveshbirangal/discord-rpc\" alt=\"Stars Badge\"/></a>\n<a href=\"https://github.com/niveshbirangal/discord-rpc/network/members\"><img src=\"https://img.shields.io/github/forks/niveshbirangal/discord-rpc\" alt=\"Forks Badge\"/></a>\n<a href=\"https://github.com/niveshbirangal/discord-rpc/issues\"><img src=\"https://img.shields.io/github/issues/niveshbirangal/discord-rpc\" alt=\"Issues Badge\"/></a>\n### Requirements:\n![Python](https://img.shields.io/badge/-Python-black?style=flat-square&logo=Python)<br>3.0 and above\n### Setup:\nclone this to your local device\n```bash\ngit clone https://github.com/niveshbirangal/discord-rpc.git\n```\nGo to the developer portal and create an application\n```bash\nhttps://discord.com/developers/applications\n```\n<img align=\"center\" src='https://github.com/niveshbirangal/discord-rpc/blob/master/readmeassets/createapp.gif'>\n<br><br />\nAdd assets(image) to your application\n<br><br>\n<img align=\"center\" src='https://github.com/niveshbirangal/discord-rpc/blob/master/readmeassets/selectimage.png'>\n<br><br /> \nSelect images and clear all fields except <code>PARTY ID</code> and <code>JOIN SECRET</code>\n<br><br>\n<img align=\"center\" src='https://github.com/niveshbirangal/discord-rpc/blob/master/readmeassets/fileds.png'>\n<br><br />\nNow go to example.py, put your client id, and change the activity code according to your best fit\n\n```bash\n activity = {\n            \"state\": \"Adobe\",  # anything you like\n            \"details\": \"Editing\",  # anything you like\n            \"timestamps\": {\n                \"start\": start_time\n            },\n            \"assets\": {\n                \"small_text\": \"Adobe\",  # anything you like\n                \"small_image\": \"adobe\",  # must match the image key\n                \"large_text\": \"Illustrator\",  # anything you like\n                \"large_image\": \"illustrator\"  # must match the image key\n            }\n        }\n```\nMake sure your desktop app is running and then run the example.py\n```bash\npython3 example.py\n```\n<img align=\"center\" src='https://github.com/niveshbirangal/discord-rpc/blob/master/readmeassets/activity.png'>\n\n<!-- ROADMAP -->\n## Roadmap\nSee the [open issues](https://github.com/niveshbirangal/discord-rpc/issues) for a list of proposed features (and known issues).\n\n<!-- CONTRIBUTING -->\n## Contributing\nContributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n<!-- LICENSE -->\n## License\nDistributed under the MIT License. See `LICENSE` for more information.\n\n\n<!-- CONTACT -->\n## Connect with me:\n[<img align=\"left\" alt=\"niveshb.com\" width=\"32px\" src=\"https://raw.githubusercontent.com/niveshbirangal/niveshbirangal/master/source/website.svg\"/>][website]\n[<img align=\"left\" alt=\"niveshbirangal | LinkedIn\" width=\"32px\" src=\"https://raw.githubusercontent.com/niveshbirangal/niveshbirangal/master/source/linkedin.svg\"/>][linkedin]\n[<img align=\"left\" alt=\"niveshbirangal | Instagram\" width=\"32px\" src=\"https://raw.githubusercontent.com/niveshbirangal/niveshbirangal/master/source/instagram.svg\"/>][instagram]\n[<img align=\"left\" alt=\"niveshbirangal | YouTube\" width=\"32px\" src=\"https://raw.githubusercontent.com/niveshbirangal/niveshbirangal/master/source/youtube.svg\"/>][youtube]\n\n\n\n[website]: https://niveshb.com\n[youtube]: https://www.youtube.com/channel/UCpwUP_HiOyG_GHluWpQK59g?view_as=subscriber\n[instagram]: https://instagram.com/niveshbirangal\n[linkedin]: https://linkedin.com/in/niveshbirangal\n"
  },
  {
    "path": "example.py",
    "content": "import rpc\nimport time\nfrom time import mktime\n\nprint(\"Demo for python-discord-rpc Adobe illustrator\")\nclient_id = '467570845993271307'  # Your application's client ID as a string. (This isn't a real client ID)\nrpc_obj = rpc.DiscordIpcClient.for_platform(client_id)  # Send the client ID to the rpc module\nprint(\"RPC connection successful.\")\n\ntime.sleep(5)\nstart_time = mktime(time.localtime())\nwhile True:\n    activity = {\n            \"state\": \"Adobe\",  # anything you like\n            \"details\": \"Editing\",  # anything you like\n            \"timestamps\": {\n                \"start\": start_time\n            },\n            \"assets\": {\n                \"small_text\": \"Adobe\",  # anything you like\n                \"small_image\": \"adobe\",  # must match the image key\n                \"large_text\": \"Illustrator\",  # anything you like\n                \"large_image\": \"illustrator\"  # must match the image key\n            }\n        }\n    rpc_obj.set_activity(activity)\n    time.sleep(900)\n"
  },
  {
    "path": "rpc.py",
    "content": "# References:\n# * https://github.com/devsnek/discord-rpc/tree/master/src/transports/IPC.js\n# * https://github.com/devsnek/discord-rpc/tree/master/example/main.js\n# * https://github.com/discordapp/discord-rpc/tree/master/documentation/hard-mode.md\n# * https://github.com/discordapp/discord-rpc/tree/master/src\n# * https://discordapp.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields\n\nfrom abc import ABCMeta, abstractmethod\nimport json\nimport logging\nimport os\nimport socket\nimport sys\nimport struct\nimport uuid\n\nOP_HANDSHAKE = 0\nOP_FRAME = 1\nOP_CLOSE = 2\nOP_PING = 3\nOP_PONG = 4\n\nlogger = logging.getLogger(__name__)\n\n\nclass DiscordIpcError(Exception):\n    pass\n\n\nclass DiscordIpcClient(metaclass=ABCMeta):\n\n    \"\"\"Work with an open Discord instance via its JSON IPC for its rich presence API.\n\n    In a blocking way.\n    Classmethod `for_platform`\n    will resolve to one of WinDiscordIpcClient or UnixDiscordIpcClient,\n    depending on the current platform.\n    Supports context handler protocol.\n    \"\"\"\n\n    def __init__(self, client_id):\n        self.client_id = client_id\n        self._connect()\n        self._do_handshake()\n        logger.info(\"connected via ID %s\", client_id)\n\n    @classmethod\n    def for_platform(cls, client_id, platform=sys.platform):\n        if platform == 'win32':\n            return WinDiscordIpcClient(client_id)\n        else:\n            return UnixDiscordIpcClient(client_id)\n\n    @abstractmethod\n    def _connect(self):\n        pass\n\n    def _do_handshake(self):\n        ret_op, ret_data = self.send_recv({'v': 1, 'client_id': self.client_id}, op=OP_HANDSHAKE)\n        # {'cmd': 'DISPATCH', 'data': {'v': 1, 'config': {...}}, 'evt': 'READY', 'nonce': None}\n        if ret_op == OP_FRAME and ret_data['cmd'] == 'DISPATCH' and ret_data['evt'] == 'READY':\n            return\n        else:\n            if ret_op == OP_CLOSE:\n                self.close()\n            raise RuntimeError(ret_data)\n\n    @abstractmethod\n    def _write(self, date: bytes):\n        pass\n\n    @abstractmethod\n    def _recv(self, size: int) -> bytes:\n        pass\n\n    def _recv_header(self) -> (int, int):\n        header = self._recv_exactly(8)\n        return struct.unpack(\"<II\", header)\n\n    def _recv_exactly(self, size) -> bytes:\n        buf = b\"\"\n        size_remaining = size\n        while size_remaining:\n            chunk = self._recv(size_remaining)\n            buf += chunk\n            size_remaining -= len(chunk)\n        return buf\n\n    def close(self):\n        logger.warning(\"closing connection\")\n        try:\n            self.send({}, op=OP_CLOSE)\n        finally:\n            self._close()\n\n    @abstractmethod\n    def _close(self):\n        pass\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *_):\n        self.close()\n\n    def send_recv(self, data, op=OP_FRAME):\n        self.send(data, op)\n        return self.recv()\n\n    def send(self, data, op=OP_FRAME):\n        logger.debug(\"sending %s\", data)\n        data_str = json.dumps(data, separators=(',', ':'))\n        data_bytes = data_str.encode('utf-8')\n        header = struct.pack(\"<II\", op, len(data_bytes))\n        self._write(header)\n        self._write(data_bytes)\n\n    def recv(self) -> (int, \"JSON\"):\n        \"\"\"Receives a packet from discord.\n\n        Returns op code and payload.\n        \"\"\"\n        op, length = self._recv_header()\n        payload = self._recv_exactly(length)\n        data = json.loads(payload.decode('utf-8'))\n        logger.debug(\"received %s\", data)\n        return op, data\n\n    def set_activity(self, act):\n        # act\n        data = {\n            'cmd': 'SET_ACTIVITY',\n            'args': {'pid': os.getpid(),\n                     'activity': act},\n            'nonce': str(uuid.uuid4())\n        }\n        self.send(data)\n\n\nclass WinDiscordIpcClient(DiscordIpcClient):\n\n    _pipe_pattern = R'\\\\?\\pipe\\discord-ipc-{}'\n\n    def _connect(self):\n        for i in range(10):\n            path = self._pipe_pattern.format(i)\n            try:\n                self._f = open(path, \"w+b\")\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n        self.path = path\n\n    def _write(self, data: bytes):\n        self._f.write(data)\n        self._f.flush()\n\n    def _recv(self, size: int) -> bytes:\n        return self._f.read(size)\n\n    def _close(self):\n        self._f.close()\n\n\nclass UnixDiscordIpcClient(DiscordIpcClient):\n\n    def _connect(self):\n        self._sock = socket.socket(socket.AF_UNIX)\n        pipe_pattern = self._get_pipe_pattern()\n\n        for i in range(10):\n            path = pipe_pattern.format(i)\n            if not os.path.exists(path):\n                continue\n            try:\n                self._sock.connect(path)\n            except OSError as e:\n                logger.error(\"failed to open {!r}: {}\".format(path, e))\n            else:\n                break\n        else:\n            return DiscordIpcError(\"Failed to connect to Discord pipe\")\n\n    @staticmethod\n    def _get_pipe_pattern():\n        env_keys = ('XDG_RUNTIME_DIR', 'TMPDIR', 'TMP', 'TEMP')\n        for env_key in env_keys:\n            dir_path = os.environ.get(env_key)\n            if dir_path:\n                break\n        else:\n            dir_path = '/tmp'\n        return os.path.join(dir_path, 'discord-ipc-{}')\n\n    def _write(self, data: bytes):\n        self._sock.sendall(data)\n\n    def _recv(self, size: int) -> bytes:\n        return self._sock.recv(size)\n\n    def _close(self):\n        self._sock.close()\n"
  }
]