├── .gitignore ├── LICENSE ├── README.md ├── api ├── __init__.py ├── macos.py └── windows.py ├── assets ├── acrobat_large.png ├── acrobat_small.png ├── aftereffects_large.png ├── aftereffects_small.png ├── audition_large.png ├── audition_small.png ├── illustrator_large.png ├── illustrator_small.png ├── indesign_large.png ├── indesign_small.png ├── photoshop_large.png ├── photoshop_small.png ├── premierepro_large.png └── premierepro_small.png ├── handler.py ├── meta.json ├── requirements.txt └── rpc.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Smokie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text][header] 2 | 3 | # Adobe Discord Rich Presence 4 | 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/719bbef946084e78b20a1c7c63420e86)](https://www.codacy.com/app/imsmokie/adobe-rpc?utm_source=github.com&utm_medium=referral&utm_content=smokes/adobe-rpc&utm_campaign=Badge_Grade) 6 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 7 | [![GitHub issues](https://img.shields.io/github/issues/smokes/adobe-rpc.svg)](https://GitHub.com/smokes/adobe-rpc/issues/) 8 | 9 | Rich Presence for Discord to display what you're currently doing in most of adobe's applications 10 | 11 | ## Requirements 12 | 13 | - Python 3.4+ with Pip 14 | - If you're running a 64bit version of python, download the pywin32 binaries from [here!](https://github.com/mhammond/pywin32/releases) 15 | - Adobe apps (obviously) 16 | 17 | ## How to use 18 | 19 | - Clone the repo `$ git clone https://github.com/smokes/adobe-rpc.git` 20 | - Make sure you're running an Adobe application. 21 | - Install the required packages `$ pip install -r requirements.txt` 22 | - Run the script `$ python rpc.py` 23 | - Enjoy! 24 | 25 | ## Binaries 26 | 27 | The easiest way to use **adobe-rpc** is to download the **.exe** from the [releases!](https://github.com/smokes/adobe-rpc/releases) page. 28 | 29 | ## Preview 30 | 31 |
32 | 33 | 34 | 35 |
36 | 37 | ## TODOs 38 | 39 | - Macos support 40 | - Better error handling 41 | - Prevent app from exiting when Discord or any app isn't running. 42 | 43 | ## Contributing 44 | 45 | **adobe-rpc** currently doesn't support: Media Encoder, Xd and Audition. Please submit a PR where you add an app that we haven't included yet to the file **meta.json**, where you specify the app's process name. 46 | 47 | [header]: https://i.imgur.com/zGFYunZ.png "Repo header" 48 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/api/__init__.py -------------------------------------------------------------------------------- /api/macos.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/api/macos.py -------------------------------------------------------------------------------- /api/windows.py: -------------------------------------------------------------------------------- 1 | import win32gui 2 | import win32process 3 | import psutil 4 | import json 5 | import ntpath 6 | 7 | 8 | def get_title(pid): 9 | def callback(hwnd, hwnds): 10 | if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd): 11 | _, found_pid = win32process.GetWindowThreadProcessId(hwnd) 12 | if found_pid == pid: 13 | hwnds.append(hwnd) 14 | hwnds = [] 15 | win32gui.EnumWindows(callback, hwnds) 16 | window_title = win32gui.GetWindowText(hwnds[-1]) 17 | return window_title 18 | 19 | 20 | with open('meta.json') as f: 21 | data = json.load(f) 22 | 23 | 24 | def get_process_info(): 25 | for element in data: 26 | process_name = element['processName'] 27 | for process in psutil.process_iter(): 28 | process_info = process.as_dict(attrs=['pid', 'name']) 29 | if process_info['name'].lower() in process_name: 30 | element['pid'] = process_info['pid'] 31 | return element 32 | 33 | 34 | def get_status(app_info, title): 35 | if app_info['largeText'].lower() in title.lower() and app_info['splitBy'] != " - ": 36 | return "{}: IDLE".format(app_info['smallText']) 37 | else: 38 | title_seperated = title.split(app_info['splitBy']) 39 | if app_info['splitBy'] == " - ": 40 | title_basename = ntpath.basename( 41 | title_seperated[app_info['splitIndex']]) 42 | return "{}: {}".format(app_info['smallText'], title_basename) 43 | else: 44 | return "{}: {}".format(app_info['smallText'], title_seperated[app_info['splitIndex']]) 45 | -------------------------------------------------------------------------------- /assets/acrobat_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/acrobat_large.png -------------------------------------------------------------------------------- /assets/acrobat_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/acrobat_small.png -------------------------------------------------------------------------------- /assets/aftereffects_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/aftereffects_large.png -------------------------------------------------------------------------------- /assets/aftereffects_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/aftereffects_small.png -------------------------------------------------------------------------------- /assets/audition_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/audition_large.png -------------------------------------------------------------------------------- /assets/audition_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/audition_small.png -------------------------------------------------------------------------------- /assets/illustrator_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/illustrator_large.png -------------------------------------------------------------------------------- /assets/illustrator_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/illustrator_small.png -------------------------------------------------------------------------------- /assets/indesign_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/indesign_large.png -------------------------------------------------------------------------------- /assets/indesign_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/indesign_small.png -------------------------------------------------------------------------------- /assets/photoshop_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/photoshop_large.png -------------------------------------------------------------------------------- /assets/photoshop_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/photoshop_small.png -------------------------------------------------------------------------------- /assets/premierepro_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/premierepro_large.png -------------------------------------------------------------------------------- /assets/premierepro_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadir/adobe-rpc/b97cf95a222121b15f251a75ecdedfea3f9e868b/assets/premierepro_small.png -------------------------------------------------------------------------------- /handler.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def get_rpc_update(): 5 | if sys.platform in ['Windows', 'win32', 'cygwin']: 6 | try: 7 | from api.windows import get_title, get_process_info, get_status 8 | 9 | app_info = get_process_info() 10 | app_title = get_title(app_info['pid']) 11 | app_state = get_status(app_info, app_title) 12 | 13 | rpc_update = {'state': app_state, 14 | 'small_image': app_info['smallImageKey'], 15 | 'large_image': app_info['largeImageKey'], 16 | 'large_text': app_info['largeText'], 17 | 'small_text': app_info['smallText'], 18 | 'details': app_info['largeText']} 19 | return rpc_update 20 | 21 | except ImportError: 22 | print("Make sure you have pywin32 installed, for more info read README.md") 23 | 24 | elif sys.platform in ['Mac', 'darwin', 'os2', 'os2emx']: 25 | print("Macos support is currently not available.") 26 | sys.exit(0) 27 | 28 | 29 | def exception_handler(exception, future): 30 | print("") 31 | -------------------------------------------------------------------------------- /meta.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "processName": "photoshop.exe", 3 | "splitBy": " @ ", 4 | "splitIndex": 0, 5 | "largeImageKey": "photoshop_large", 6 | "smallImageKey": "photoshop_small", 7 | "largeText": "Adobe Photoshop", 8 | "smallText": "Editing" 9 | 10 | }, 11 | { 12 | "processName": "adobe premiere pro.exe", 13 | "splitBy": " - ", 14 | "splitIndex": 1, 15 | "largeImageKey": "premierepro_large", 16 | "smallImageKey": "premierepro_small", 17 | "largeText": "Adobe Premiere Pro", 18 | "smallText": "Editing" 19 | }, 20 | { 21 | "processName": "illustrator.exe", 22 | "splitBy": " @ ", 23 | "splitIndex": 0, 24 | "largeImageKey": "illustrator_large", 25 | "smallImageKey": "illustrator_small", 26 | "largeText": "Adobe Illustrator", 27 | "smallText": "Editing" 28 | }, 29 | { 30 | "processName": "afterfx.exe", 31 | "splitBy": " - ", 32 | "splitIndex": 1, 33 | "largeImageKey": "aftereffects_large", 34 | "smallImageKey": "aftereffects_small", 35 | "largeText": "Adobe After Effects", 36 | "smallText": "Editing" 37 | }, 38 | { 39 | "processName": "acrobat.exe", 40 | "splitBy": " - ", 41 | "splitIndex": 0, 42 | "largeImageKey": "acrobat_large", 43 | "smallImageKey": "acrobat_small", 44 | "largeText": "Adobe Acrobat", 45 | "smallText": "Reading" 46 | }, 47 | { 48 | "processName": "indesign.exe", 49 | "splitBy": " @ ", 50 | "splitIndex": 0, 51 | "largeImageKey": "indesign_large", 52 | "smallImageKey": "indesign_small", 53 | "largeText": "Adobe InDesign", 54 | "smallText": "Editing" 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pypresence 2 | pywin32 3 | psutil -------------------------------------------------------------------------------- /rpc.py: -------------------------------------------------------------------------------- 1 | from pypresence import Presence 2 | import handler 3 | import time 4 | 5 | client_id = "482150417455775755" 6 | rich_presence = Presence(client_id) 7 | 8 | def connect(): 9 | return rich_presence.connect() 10 | 11 | def connect_loop(retries=0): 12 | if retries > 10: 13 | return 14 | try: 15 | connect() 16 | except: 17 | print("Error connecting to Discord") 18 | time.sleep(10) 19 | retries += 1 20 | connect_loop(retries) 21 | else: 22 | update_loop() 23 | 24 | print("Started Adobe RPC") 25 | 26 | def update_loop(): 27 | start_time = int(time.time()) 28 | try: 29 | while True: 30 | rpc_data = handler.get_rpc_update() 31 | rich_presence.update(state=rpc_data['state'], 32 | small_image=rpc_data['small_image'], 33 | large_image=rpc_data['large_image'], 34 | large_text=rpc_data['large_text'], 35 | small_text=rpc_data['small_text'], 36 | details=rpc_data['details'], 37 | start=start_time) 38 | time.sleep(15) 39 | except: 40 | rich_presence.clear() 41 | print("Run Adobe/Discord app") 42 | time.sleep(5) 43 | update_loop() 44 | 45 | try: 46 | connect_loop() 47 | except KeyboardInterrupt: 48 | print("Stopped Adobe RPC") 49 | --------------------------------------------------------------------------------