This commit is contained in:
Zoey 2024-05-28 19:42:10 +02:00
parent f934b7ff99
commit 5c9564c703
Signed by: SailorZoop
GPG key ID: 854F554AFF0A96D1
35 changed files with 5410 additions and 3 deletions

44
L10n/de.strings Normal file
View file

@ -0,0 +1,44 @@
/* Language name */
"!Language" = "Deutsch";
/* Idle message. [load.py] */
"Connecting CMDR Interface" = "Verbindet mit KMDT-Schnittstelle";
/* Details of system. [load.py] */
"In system {system}" = "Im System {system}";
/* If docked. [load.py] */
"Docked at {station}" = "Angedockt an {station}";
/* While jumping. [load.py] */
"Jumping" = "Springt";
/* If Hyperspace jumping. [load.py] */
"Jumping to system {system}" = "Springt zum System {system}";
/* If Supercruise jumping. [load.py] */
"Preparing for supercruise" = "Macht sich bereit für Supercruise";
/* When supercruising. [load.py] */
"Supercruising" = "Im Supercruise";
/* When in normal space. [load.py] */
"Flying in normal space" = "Fliegt im normalen Raum";
/* When in normal space and near a station. [load.py] */
"Flying near {station}" = "Fliegt nahe {station}";
/* When approaching a body. [load.py] */
"Approaching {body}" = "Nähert sich {body}";
/* When landed on a body. [load.py] */
"Landed on {body}" = "Gelandet auf {body}";
/* After taking off from a body. [load.py] */
"Flying around {body}" = "Fliegt um {body}";
/* When in SRV. [load.py] */
"In SRV on {body}" = "Im SRV auf {body}";
/* When in SRV and ship has taken off. [load.py] */
"In SRV on {body}, ship in orbit" = "Im SRV auf {body}, Schiff im Orbit";

44
L10n/en.template Normal file
View file

@ -0,0 +1,44 @@
/* Language name */
"!Language" = "English";
/* Idle message. [load.py] */
"Connecting CMDR Interface" = "Connecting CMDR Interface";
/* Details of system. [load.py] */
"In system {system}" = "In system {system}";
/* If docked. [load.py] */
"Docked at {station}" = "Docked at {station}";
/* While jumping. [load.py] */
"Jumping" = "Jumping";
/* If Hyperspace jumping. [load.py] */
"Jumping to system {system}" = "Jumping to system {system}";
/* If Supercruise jumping. [load.py] */
"Preparing for supercruise" = "Preparing for supercruise";
/* When supercruising. [load.py] */
"Supercruising" = "Supercruising";
/* When in normal space. [load.py] */
"Flying in normal space" = "Flying in normal space";
/* When in normal space and near a station. [load.py] */
"Flying near {station}" = "Flying near {station}";
/* When approaching a body. [load.py] */
"Approaching {body}" = "Approaching {body}";
/* When landed on a body. [load.py] */
"Landed on {body}" = "Landed on {body}";
/* After taking off from a body. [load.py] */
"Flying around {body}" = "Flying around {body}";
/* When in SRV. [load.py] */
"In SRV on {body}" = "In SRV on {body}";
/* When in SRV and ship has taken off. [load.py] */
"In SRV on {body}, ship in orbit" = "In SRV on {body}, ship in orbit";

44
L10n/fr.strings Normal file
View file

@ -0,0 +1,44 @@
/* Language name */
"!Language" = "Français";
/* Idle message. [load.py] */
"Connecting CMDR Interface" = "Interfaçage au Vaisseau";
/* Details of system. [load.py] */
"In system {system}" = "Dans le système {system}";
/* If docked. [load.py] */
"Docked at {station}" = "Docké à {station}";
/* While jumping. [load.py] */
"Jumping" = "Saut";
/* If Hyperspace jumping. [load.py] */
"Jumping to system {system}" = "Saut vers {system}";
/* If Supercruise jumping. [load.py] */
"Preparing for supercruise" = "Préparation d\'un saut en supercruise";
/* When supercruising. [load.py] */
"Supercruising" = "Supercruise";
/* When in normal space. [load.py] */
"Flying in normal space" = "En vol dans l\'espace";
/* When in normal space and near a station. [load.py] */
"Flying near {station}" = "En vol près de {station}";
/* When approaching a body. [load.py] */
"Approaching {body}" = "En approche de {body}";
/* When landed on a body. [load.py] */
"Landed on {body}" = "Posé sur {body}";
/* After taking off from a body. [load.py] */
"Flying around {body}" = "Vol autour de {body}";
/* When in SRV. [load.py] */
"In SRV on {body}" = "En SRV sur {body}";
/* When in SRV and ship has taken off. [load.py] */
"In SRV on {body}, ship in orbit" = "En SRV sur {body}, vaisseau en orbite";

44
L10n/pt-BR.strings Normal file
View file

@ -0,0 +1,44 @@
/* Language name */
"!Language" = "Português (Brasil)";
/* Idle message. [load.py] */
"Connecting CMDR Interface" = "Conectando Interface do CMDT";
/* Details of system. [load.py] */
"In system {system}" = "No sistema {system}";
/* If docked. [load.py] */
"Docked at {station}" = "Docado em {station}";
/* While jumping. [load.py] */
"Jumping" = "Saltando";
/* If Hyperspace jumping. [load.py] */
"Jumping to system {system}" = "Saltando para systema {system}";
/* If Supercruise jumping. [load.py] */
"Preparing for supercruise" = "Preparando para supercruseiro";
/* When supercruising. [load.py] */
"Supercruising" = "Supercrusando";
/* When in normal space. [load.py] */
"Flying in normal space" = "Voando em espaço normal";
/* When in normal space and near a station. [load.py] */
"Flying near {station}" = "Voando perto de {station}";
/* When approaching a body. [load.py] */
"Approaching {body}" = "Aproximando-se de {body}";
/* When landed on a body. [load.py] */
"Landed on {body}" = "Pousado em {body}";
/* After taking off from a body. [load.py] */
"Flying around {body}" = "Voando ao redor de {body}";
/* When in SRV. [load.py] */
"In SRV on {body}" = "No SRV em {body}";
/* When in SRV and ship has taken off. [load.py] */
"In SRV on {body}, in orbit" = "No SRV em {body}, nave em órbita";

44
L10n/ru.strings Normal file
View file

@ -0,0 +1,44 @@
/* Language name */
"!Language" = "Русский";
/* Idle message. [load.py] */
"Connecting CMDR Interface" = "Подключение интерфейса КМДР";
/* Details of system. [load.py] */
"In system {system}" = "В системе {system}";
/* If docked. [load.py] */
"Docked at {station}" = "Пристыкован к {station}";
/* While jumping. [load.py] */
"Jumping" = "Гиперпрыжок";
/* If Hyperspace jumping. [load.py] */
"Jumping to system {system}" = "Гиперпрыжок в систему {system}";
/* If Supercruise jumping. [load.py] */
"Preparing for supercruise" = "Подготовка к переходу в супереркруиз";
/* When supercruising. [load.py] */
"Supercruising" = "Полет в суперкруизе";
/* When in normal space. [load.py] */
"Flying in normal space" = "Полет в обычном космосе";
/* When in normal space and near a station. [load.py] */
"Flying near {station}" = "Полет вблизи {station}";
/* When approaching a body. [load.py] */
"Approaching {body}" = "Приближение к {body}";
/* When landed on a body. [load.py] */
"Landed on {body}" = "Посадка на {body}";
/* After taking off from a body. [load.py] */
"Flying around {body}" = "Полет возле {body}";
/* When in SRV. [load.py] */
"In SRV on {body}" = "В ТРП на поверхности {body}";
/* When in SRV and ship has taken off. [load.py] */
"In SRV on {body}, ship in orbit" = "В ТРП на поверхности {body}, судно на орбите";

View file

@ -1,3 +1,46 @@
# EliteRPC # EDMC-Discord-Presence
Discord Rich Presence for Elite Dangerous A plugin for [Elite Dangerous Market Connector](https://github.com/Marginal/EDMarketConnector) that enables [Discord Rich Presence](https://discordapp.com/rich-presence) for [Elite Dangerous](https://www.elitedangerous.com/)
Show your current location to your friends on Discord from your user profile.
![Presence Screenshot](EDMC_Discord_Presence_1.png?raw=true)
## Installation
1. [Install EDMC according to instructions](https://github.com/Marginal/EDMarketConnector)
2. Download the latest version of the plugin from [here](https://github.com/SayakMukhopadhyay/EDMC-Discord-Presence/releases). Make sure to download the release `.zip` and not the source code bundle.
3. Open Elite Dangerous Market Connector and go to File -> Settings. Then browse to the plugins tab:
![Plugin Installation](EDMC_Discord_Presence_2.png?raw=true)
4. Click "Open" to open the plugins directory.
5. Open the Zip file we have downloaded and drag the folder from within into the plugins directory
6. Restart EDMC for the plugin to take effect.
To check if the plugin is loaded correctly, go File -> Settings. Then browse to the plugins tab. `DiscordPresence` must be listed under `Enabled Plugins`
![Plugin Installation Check](EDMC_Discord_Presence_3.png?raw=true)
## Options
You can set the plugin to not show your game data. Go to File -> Settings. Under the `DiscordPresence` tab, check `Disable Presence`
![Plugin Disable](EDMC_Discord_Presence_4.png?raw=true)
## Contributing
If you find a bug, please create an issue in the issue tracker in Github, properly detailing the bug and reproduction steps.
If you are willing to contribute to the project, please work on a fork and create a pull request.
## Credits
For the CMDRs by a CMDR. Created by [CMDR Garud](https://forums.frontier.co.uk/member.php/136073-Garud) for an awesome gaming community.
A big thanks to [Jonathan Harris (Marginal)](https://github.com/Marginal) for creating the Python boilerplate code for the Discord Rich Presence SDK. Without his input, the plugin would not have been done. Special mention for the awesome group I am in, [Knights of Karma](http://knightsofkarma.com/), for their continuous support.
Translate to french, migrate from python2 to python3 by [Poneyy](https://github.com/Poneyy)
## License
Developed under [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/).

Binary file not shown.

BIN
lib/discord_game_sdk.dll Normal file

Binary file not shown.

BIN
lib/discord_game_sdk.dylib Normal file

Binary file not shown.

BIN
lib/discord_game_sdk.so Normal file

Binary file not shown.

356
load.py Normal file
View file

@ -0,0 +1,356 @@
#
# KodeBlox Copyright 2019 Sayak Mukhopadhyay
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http: //www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import functools
import logging
import threading
import tkinter as tk
from os.path import dirname, join
import semantic_version
import sys
import time
import l10n
import myNotebook as nb
from config import config, appname, appversion
from py_discord_sdk import discordsdk as dsdk
plugin_name = "DiscordPresenceNG"
logger = logging.getLogger(f'{appname}.{plugin_name}')
_ = functools.partial(l10n.Translations.translate, context=__file__)
CLIENT_ID = 1013117837310693438
VERSION = '1.0'
# Add global var for Planet name (landing + around)
planet = '<Hidden>'
landingPad = '2'
this = sys.modules[__name__] # For holding module globals
def ship(ship_type):
ship_name = ''
match ship_type:
case "sidewinder":
ship_name = "Sidewinder"
case "eagle":
ship_name = "Eagle"
case "hauler":
ship_name = "Hauler"
case "adder":
ship_name = "Adder"
case "adder_taxi":
ship_name = "Apex Shuttle"
case "empire_eagle":
ship_name = "Imperial Eagle"
case "viper":
ship_name = "Viper Mk III"
case "cobramkiii":
ship_name = "Cobra Mk III"
case "viper_mkiv":
ship_name = "Viper Mk IV"
case "diamondback":
ship_name = "Diamondback Scout"
case "cobramkiv":
ship_name = "Cobra Mk IV"
case "type6":
ship_name = "Type-6 Transporter"
case "dolphin":
ship_name = "Dolphin"
case "diamondbackxl":
ship_name = "Diamondback Explorer"
case "empire_courier":
ship_name = "Imperial Courier"
case "independant_trader":
ship_name = "Keelback"
case "asp_scout":
ship_name = "Asp Scout"
case "vulture":
ship_name = "Vulture"
case "asp":
ship_name = "Asp Explorer"
case "federation_dropship":
ship_name = "Federal Dropship"
case "type7":
ship_name = "Type-7 Transporter"
case "typex":
ship_name = "Alliance Chieftain"
case "federation_dropship_mkii":
ship_name = "Federal Assault Ship"
case "empire_trader":
ship_name = "Imperial Clipper"
case "typex_2":
ship_name = "Alliance Crusader"
case "typex_3":
ship_name = "Alliance Challenger"
case "federation_gunship":
ship_name = "Federal Gunship"
case "krait_light":
ship_name = "Krait Phantom"
case "krait_mkii":
ship_name = "Krait Mk II"
case "orca":
ship_name = "Orca"
case "ferdelance":
ship_name = "Fer-de-Lance"
case "mamba":
ship_name = "Mamba"
case "python":
ship_name = "Python"
case "type9":
ship_name = "Type-9 Heavy"
case "belugaliner":
ship_name = "Beluga Liner"
case "type9_military":
ship_name = "Type-10 Defender"
case "anaconda":
ship_name = "Anaconda"
case "federation_corvette":
ship_name = "Federal Corvette"
case "cutter":
ship_name = "Imperial Cutter"
case _: # Default case for any other value
ship_name = "Unknown Ship"
return ship_name
def callback(result):
logger.info(f'Callback: {result}')
if result == dsdk.Result.ok:
logger.info("Successfully set the activity!")
elif result == dsdk.Result.transaction_aborted:
logger.warning(f'Transaction aborted due to SDK shutting down: {result}')
else:
logger.error(f'Error in callback: {result}')
raise Exception(result)
def update_presence():
if isinstance(appversion, str):
core_version = semantic_version.Version(appversion)
elif callable(appversion):
core_version = appversion()
logger.info(f'Core EDMC version: {core_version}')
if core_version < semantic_version.Version('5.0.0-beta1'):
logger.info('EDMC core version is before 5.0.0-beta1')
if config.getint("disable_presence") == 0:
this.activity.state = this.presence_state
this.activity.details = this.presence_details
this.activity.assets.large_image = this.presence_large_image
this.activity.assets.large_text = this.presence_large_text
this.activity.assets.small_image = this.presence_small_image
this.activity.assets.small_text = this.presence_small_text
else:
logger.info('EDMC core version is at least 5.0.0-beta1')
if config.get_int("disable_presence") == 0:
this.activity.state = this.presence_state
this.activity.details = this.presence_details
this.activity.assets.large_image = this.presence_large_image
this.activity.assets.large_text = this.presence_large_text
this.activity.assets.small_image = this.presence_small_image
this.activity.assets.small_text = this.presence_small_text
this.activity.timestamps.start = int(this.time_start)
this.activity_manager.update_activity(this.activity, callback)
def plugin_prefs(parent, cmdr, is_beta):
"""
Return a TK Frame for adding to the EDMC settings dialog.
"""
if isinstance(appversion, str):
core_version = semantic_version.Version(appversion)
elif callable(appversion):
core_version = appversion()
logger.info(f'Core EDMC version: {core_version}')
if core_version < semantic_version.Version('5.0.0-beta1'):
logger.info('EDMC core version is before 5.0.0-beta1')
this.disablePresence = tk.IntVar(value=config.get_int("disable_presence"))
else:
logger.info('EDMC core version is at least 5.0.0-beta1')
this.disablePresence = tk.IntVar(value=config.get_int("disable_presence"))
frame = nb.Frame(parent)
nb.Checkbutton(frame, text="Disable Presence", variable=this.disablePresence).grid()
nb.Label(frame, text='Version %s' % VERSION).grid(padx=10, pady=10, sticky=tk.W)
return frame
def prefs_changed(cmdr, is_beta):
"""
Save settings.
"""
config.set('disable_presence', this.disablePresence.get())
config.set('disable_presence', this.disableName.get())
update_presence()
def plugin_start3(plugin_dir):
this.plugin_dir = plugin_dir
this.discord_thread = threading.Thread(target=check_run, args=(plugin_dir,))
this.discord_thread.setDaemon(True)
this.discord_thread.start()
return 'DiscordPresenceNG'
def plugin_stop():
this.activity_manager.clear_activity(callback)
this.call_back_thread = None
def journal_entry(cmdr, is_beta, system, station, entry, state):
global planet
global landingPad
presence_state = this.presence_state
presence_details = this.presence_details
presence_largeimage = this.presence_large_image
presence_largetext = this.presence_large_text
presence_smallimage = this.presence_small_image
presence_smalltext = this.presence_small_text
if entry['event'] == 'StartUp':
presence_largetext = ship(('{ship}').format(ship=state['ShipType']))
presence_smalltext = ('CMDR {cmdr}').format(cmdr=cmdr)
presence_largeimage = ('{ship}').format(ship=state['ShipType'])
presence_smallimage = 'edlogo'
#presence_smalltext = ('{cmdr}cr').format(credits=state['Credits'])
presence_state = _('In system {system}').format(system=system)
if station is None:
presence_details = _('Flying in normal space')
else:
presence_details = _('Docked at {station}').format(station=station)
elif entry['event'] == 'Location':
presence_state = _('In system {system}').format(system=system)
if station is None:
presence_details = _('Flying in normal space')
else:
presence_details = _('Docked at {station}').format(station=station)
elif entry['event'] == 'StartJump':
presence_state = _('Jumping')
if entry['JumpType'] == 'Hyperspace':
presence_details = _('Jumping to system {system}').format(system=entry['StarSystem'])
elif entry['JumpType'] == 'Supercruise':
presence_details = _('Preparing for supercruise')
elif entry['event'] == 'SupercruiseEntry':
presence_state = _('In system {system}').format(system=system)
presence_details = _('Supercruising')
elif entry['event'] == 'SupercruiseExit':
presence_state = _('In system {system}').format(system=system)
presence_details = _('Flying in normal space')
elif entry['event'] == 'FSDJump':
presence_state = _('In system {system}').format(system=system)
presence_details = _('Supercruising')
elif entry['event'] == 'Docked':
presence_state = _('In system {system}').format(system=system)
presence_details = _('Docked at {station}').format(station=station)
elif entry['event'] == 'Undocked':
presence_state = _('In system {system}').format(system=system)
presence_details = _('Flying in normal space')
elif entry['event'] == 'ShutDown':
presence_state = _('Main Menu')
presence_details = ''
elif entry['event'] == 'DockingGranted':
landingPad = entry['LandingPad']
elif entry['event'] == 'Music':
if entry['MusicTrack'] == 'MainMenu':
presence_state = _('In Main Menu')
presence_details = ''
# Todo: This elif might not be executed on undocked. Functionality can be improved
elif entry['event'] == 'Undocked' or entry['event'] == 'DockingCancelled' or entry['event'] == 'DockingTimeout':
presence_details = _('Flying near {station}').format(station=entry['StationName'])
# Planetary events
elif entry['event'] == 'ApproachBody':
presence_details = _('Approaching {body}').format(body=entry['Body'])
planet = entry['Body']
elif entry['event'] == 'Touchdown' and entry['PlayerControlled']:
presence_details = _('Landed on {body}').format(body=planet)
elif entry['event'] == 'Liftoff' and entry['PlayerControlled']:
if entry['PlayerControlled']:
presence_details = _('Flying around {body}').format(body=planet)
else:
presence_details = _('In SRV on {body}, ship in orbit').format(body=planet)
elif entry['event'] == 'LeaveBody':
presence_details = _('Supercruising')
elif entry['event'] == 'Loadout':
presence_largeimage = ('{ship}').format(ship=state['ShipType'])
presence_largetext = ship(('{ship}').format(ship=state['ShipType']))
# EXTERNAL VEHICLE EVENTS
elif entry['event'] == 'LaunchSRV':
presence_details = _('In SRV on {body}').format(body=planet)
elif entry['event'] == 'DockSRV':
presence_details = _('Landed on {body}').format(body=planet)
if (presence_state != this.presence_state or
presence_details != this.presence_details or
presence_largeimage != this.presence_large_image or
presence_largetext != this.presence_large_text or
presence_smallimage != this.presence_small_image or
presence_smalltext != this.presence_small_text) :
this.presence_state = presence_state
this.presence_details = presence_details
this.presence_large_image = presence_largeimage
this.presence_large_text = presence_largetext
this.presence_small_image = presence_smallimage
this.presence_small_text = presence_smalltext
update_presence()
def check_run(plugin_dir):
plugin_path = join(dirname(plugin_dir), plugin_name)
retry = True
while retry:
time.sleep(1 / 10)
try:
this.app = dsdk.Discord(CLIENT_ID, dsdk.CreateFlags.no_require_discord, plugin_path)
retry = False
except Exception:
pass
this.activity_manager = this.app.get_activity_manager()
this.activity = dsdk.Activity()
this.call_back_thread = threading.Thread(target=run_callbacks)
this.call_back_thread.setDaemon(True)
this.call_back_thread.start()
this.presence_state = _('Connecting CMDR Interface')
this.presence_details = ''
this.presence_large_image = 'edlogo'
this.presence_large_text = 'EliteRPC'
this.presence_small_image = ''
this.presence_small_text = ''
this.time_start = time.time()
this.disablePresence = None
this.disableName = None
update_presence()
def run_callbacks():
try:
while True:
time.sleep(1 / 10)
this.app.run_callbacks()
except Exception:
check_run(this.plugin_dir)

157
py_discord_sdk/.gitignore vendored Normal file
View file

@ -0,0 +1,157 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Pycharm files
.idea/*
.idea
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode
.idea/vcs.xml
.DS_Store
.vscode/

22
py_discord_sdk/LICENSE Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 NathaanTFM
Copyright (c) 2020 LennyPhoenix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

134
py_discord_sdk/README.md Normal file
View file

@ -0,0 +1,134 @@
# Discord Game SDK for Python
A Python wrapper around Discord's Game SDK.
**NOTE**: This is entirely experimental, and may not work as intended. Please report all bugs to the [GitHub issue tracker](https://github.com/LennyPhoenix/py-discord-sdk/issues).
**Credit to [NathaanTFM](https://github.com/NathaanTFM) for creating the [original library](https://github.com/NathaanTFM/discord-game-sdk-python).**
## Installation
- Install the module:
- With `PIP`:
- Stable: `python -m pip install discordsdk`
- Latest: `python -m pip install git+https://github.com/LennyPhoenix/py-discord-sdk.git`
- With `setup.py` (latest):
- `git clone https://github.com/LennyPhoenix/py-discord-sdk.git`
- `cd py-discord-sdk`
- `python -m setup install`
- Download [Discord Game SDK (2.5.6)](https://dl-game-sdk.discordapp.net/2.5.6/discord_game_sdk.zip).
- Grab the DLL from `discord_game_sdk.zip` in the `lib` directory and put it in your project's `lib` directory.
## Documentation
If you need documentation, look at [**the official Game SDK docs**](https://discord.com/developers/docs/game-sdk/sdk-starter-guide); this was made following the official documentation.
We also have some [work-in-progress wiki docs](https://github.com/LennyPhoenix/py-discord-sdk/wiki).
## Features
- Should be working:
- **ActivityManager**
- **ImageManager**
- **NetworkManager**
- **RelationshipManager**
- **StorageManager**
- **UserManager**
- Should be working, but need more testing:
- **AchievementManager** (not tested at all)
- **ApplicationManager** (especially the functions `GetTicket` and `ValidateOrExit`)
- **LobbyManager**
- **OverlayManager**
- **StoreManager** (not tested at all)
- **VoiceManager**
## Contributing
The code needs **more comments, type hinting**. You can also implement the **missing features**, or add **more tests**. Feel free to open a **pull request**!
You can also **report issues**. Just open an issue and I will look into it!
### Todo List
- Better organisation of submodules.
- CI/CD.
- Update sdk.py to use type annotations.
- Update to Discord SDK 3.2.0.
## Examples
You can find more examples in the `examples/` directory.
### Create a Discord instance
```python
import time
import discordsdk as dsdk
app = dsdk.Discord(APPLICATION_ID, dsdk.CreateFlags.default)
# Don't forget to call run_callbacks
while 1:
time.sleep(1/10)
app.run_callbacks()
```
### Get current user
```python
import time
import discordsdk as dsdk
app = dsdk.Discord(APPLICATION_ID, dsdk.CreateFlags.default)
user_manager = app.get_user_manager()
def on_curr_user_update():
user = user_manager.get_current_user()
print(f"Current user : {user.username}#{user.discriminator}")
user_manager.on_current_user_update = on_curr_user_update
# Don't forget to call run_callbacks
while 1:
time.sleep(1/10)
app.run_callbacks()
```
### Set activity
```python
import time
import discordsdk as dsdk
app = dsdk.Discord(APPLICATION_ID, dsdk.CreateFlags.default)
activity_manager = app.get_activity_manager()
activity = dsdk.Activity()
activity.state = "Testing Game SDK"
activity.party.id = "my_super_party_id"
activity.party.size.current_size = 4
activity.party.size.max_size = 8
activity.secrets.join = "my_super_secret"
def callback(result):
if result == dsdk.Result.ok:
print("Successfully set the activity!")
else:
raise Exception(result)
activity_manager.update_activity(activity, callback)
# Don't forget to call run_callbacks
while 1:
time.sleep(1/10)
app.run_callbacks()
```

View file

@ -0,0 +1,55 @@
from . import sdk
from .achievement import AchievementManager
from .activity import ActivityManager
from .application import ApplicationManager
from .discord import Discord
from .enum import (
ActivityActionType, ActivityJoinRequestReply, ActivityType, CreateFlags,
EntitlementType, ImageType, InputModeType, LobbySearchCast,
LobbySearchComparison, LobbySearchDistance, LobbyType, LogLevel,
PremiumType, RelationshipType, Result, SkuType, Status, UserFlag)
from .event import bind_events
from .exception import DiscordException, exceptions, get_exception
from .image import ImageManager
from .lobby import (LobbyManager, LobbyMemberTransaction, LobbySearchQuery,
LobbyTransaction)
from .model import (
Activity, ActivityAssets, ActivityParty, ActivitySecrets,
ActivityTimestamps, Entitlement, FileStat, ImageDimensions, ImageHandle,
InputMode, Lobby, Model, OAuth2Token, PartySize, Presence, Relationship,
Sku, SkuPrice, User, UserAchievement)
from .network import NetworkManager
from .overlay import OverlayManager
from .relationship import RelationshipManager
from .storage import StorageManager
from .store import StoreManager
from .user import UserManager
from .voice import VoiceManager
__all__ = [
"sdk",
"AchievementManager",
"ActivityManager",
"ApplicationManager",
"Discord",
"ActivityActionType", "ActivityJoinRequestReply", "ActivityType", "CreateFlags",
"EntitlementType", "ImageType", "InputModeType", "LobbySearchCast",
"LobbySearchComparison", "LobbySearchDistance", "LobbyType", "LogLevel",
"PremiumType", "RelationshipType", "Result", "SkuType", "Status", "UserFlag",
"bind_events",
"DiscordException", "exceptions", "get_exception",
"ImageManager",
"LobbyManager", "LobbyMemberTransaction", "LobbySearchQuery",
"LobbyTransaction",
"Activity", "ActivityAssets", "ActivityParty", "ActivitySecrets",
"ActivityTimestamps", "Entitlement", "FileStat", "ImageDimensions", "ImageHandle",
"InputMode", "Lobby", "Model", "OAuth2Token", "PartySize", "Presence", "Relationship",
"Sku", "SkuPrice", "User", "UserAchievement",
"NetworkManager",
"OverlayManager",
"RelationshipManager",
"StorageManager",
"StoreManager",
"UserManager",
"VoiceManager",
]

View file

@ -0,0 +1,110 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .event import bind_events
from .exception import get_exception
from .model import UserAchievement
class AchievementManager:
_internal: sdk.IDiscordAchievementManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordAchievementEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordAchievementEvents,
self._on_user_achievement_update
)
def _on_user_achievement_update(self, event_data, user_achievement):
self.on_user_achievement_update(UserAchievement(copy=user_achievement.contents))
def set_user_achievement(
self,
achievement_id: int,
percent_complete: int,
callback: t.Callable[[Result], None]
) -> None:
"""
Updates the current user's status for a given achievement.
Returns discordsdk.enum.Result via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.set_user_achievement.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.set_user_achievement(
self._internal,
achievement_id,
percent_complete,
ctypes.c_void_p(),
c_callback
)
def fetch_user_achievements(self, callback: t.Callable[[Result], None]) -> None:
"""
Loads a stable list of the current user's achievements to iterate over.
Returns discordsdk.enum.Result via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.fetch_user_achievements.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.fetch_user_achievements(self._internal, ctypes.c_void_p(), c_callback)
def count_user_achievements(self) -> int:
"""
Counts the list of a user's achievements for iteration.
"""
count = ctypes.c_int32()
self._internal.count_user_achievements(self._internal, count)
return count.value
def get_user_achievement_at(self, index: int) -> UserAchievement:
"""
Gets the user's achievement at a given index of their list of achievements.
"""
achievement = sdk.DiscordUserAchievement()
result = Result(self._internal.get_user_achievement_at(
self._internal,
index,
achievement
))
if result != Result.ok:
raise get_exception(result)
return UserAchievement(internal=achievement)
def get_user_achievement(self, achievement_id: int) -> None:
"""
Gets the user achievement for the given achievement id.
"""
achievement = sdk.DiscordUserAchievement()
result = Result(self._internal.get_user_achievement(
self._internal,
achievement_id,
achievement
))
if result != Result.ok:
raise get_exception(result)
return UserAchievement(internal=achievement)
def on_user_achievement_update(self, achievement: UserAchievement) -> None:
"""
Fires when an achievement is updated for the currently connected user
"""

View file

@ -0,0 +1,179 @@
import ctypes
import typing as t
from . import sdk
from .enum import ActivityActionType, ActivityJoinRequestReply, Result
from .event import bind_events
from .model import Activity, User
class ActivityManager:
_internal: sdk.IDiscordActivityManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordActivityEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordActivityEvents,
self._on_activity_join,
self._on_activity_spectate,
self._on_activity_join_request,
self._on_activity_invite
)
def _on_activity_join(self, event_data, secret):
self.on_activity_join(secret.decode("utf8"))
def _on_activity_spectate(self, event_data, secret):
self.on_activity_spectate(secret.decode("utf8"))
def _on_activity_join_request(self, event_data, user):
self.on_activity_join_request(User(copy=user.contents))
def _on_activity_invite(self, event_data, type, user, activity):
self.on_activity_invite(type, User(copy=user.contents), Activity(copy=activity.contents))
def register_command(self, command: str) -> Result:
"""
Registers a command by which Discord can launch your game.
"""
result = Result(self._internal.register_command(self._internal, command.encode("utf8")))
return result
def register_steam(self, steam_id: int) -> Result:
"""
Registers your game's Steam app id for the protocol `steam://run-game-id/<id>`.
"""
result = Result(self._internal.register_steam(self._internal, steam_id))
return result
def update_activity(self, activity: Activity, callback: t.Callable[[Result], None]) -> None:
"""
Set a user's presence in Discord to a new activity.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.update_activity.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.update_activity(
self._internal,
activity._internal,
ctypes.c_void_p(),
c_callback
)
def clear_activity(self, callback: t.Callable[[Result], None]) -> None:
"""
Clears a user's presence in Discord to make it show nothing.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.clear_activity.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.clear_activity(self._internal, ctypes.c_void_p(), c_callback)
def send_request_reply(
self,
user_id: int,
reply: ActivityJoinRequestReply,
callback: t.Callable[[Result], None]
) -> None:
"""
Sends a reply to an Ask to Join request.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.send_request_reply.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.send_request_reply(
self._internal,
user_id,
reply,
ctypes.c_void_p(),
c_callback
)
def send_invite(
self,
user_id: int,
type: ActivityActionType,
content: str,
callback: t.Callable[[Result], None]
) -> None:
"""
Sends a game invite to a given user.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.send_invite.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.send_invite(
self._internal,
user_id,
type,
content.encode("utf8"),
ctypes.c_void_p(),
c_callback
)
def accept_invite(self, user_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Accepts a game invitation from a given userId.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.accept_invite.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.accept_invite(self._internal, user_id, ctypes.c_void_p(), c_callback)
def on_activity_join(self, join_secret: str) -> None:
"""
Fires when a user accepts a game chat invite or receives confirmation from Asking to Join.
"""
def on_activity_spectate(self, spectate_secret: str) -> None:
"""
Fires when a user accepts a spectate chat invite or clicks the Spectate button on a user's
profile.
"""
def on_activity_join_request(self, user: User) -> None:
"""
Fires when a user asks to join the current user's game.
"""
def on_activity_invite(self, type: ActivityActionType, user: User, activity: Activity) -> None:
"""
Fires when the user receives a join or spectate invite.
"""

View file

@ -0,0 +1,85 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .model import OAuth2Token
class ApplicationManager:
_internal: sdk.IDiscordApplicationManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordApplicationEvents = None
def __init__(self):
self._garbage = []
def get_current_locale(self) -> str:
"""
Get the locale the current user has Discord set to.
"""
locale = sdk.DiscordLocale()
self._internal.get_current_locale(self._internal, locale)
return locale.value.decode("utf8")
def get_current_branch(self) -> str:
"""
Get the name of pushed branch on which the game is running.
"""
branch = sdk.DiscordBranch()
self._internal.get_current_branch(self._internal, branch)
return branch.value.decode("utf8")
def get_oauth2_token(
self,
callback: t.Callable[[Result, t.Optional[OAuth2Token]], None]
) -> None:
"""
Retrieve an oauth2 bearer token for the current user.
Returns discordsdk.enum.Result (int) and OAuth2Token (str) via callback.
"""
def c_callback(callback_data, result, oauth2_token):
self._garbage.remove(c_callback)
if result == Result.ok:
callback(result, OAuth2Token(copy=oauth2_token.contents))
else:
callback(result, None)
c_callback = self._internal.get_oauth2_token.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.get_oauth2_token(self._internal, ctypes.c_void_p(), c_callback)
def validate_or_exit(self, callback: t.Callable[[Result], None]) -> None:
"""
Checks if the current user has the entitlement to run this game.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
callback(result)
c_callback = self._internal.validate_or_exit.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.validate_or_exit(self._internal, ctypes.c_void_p(), c_callback)
def get_ticket(self, callback: t.Callable[[Result, t.Optional[str]], None]) -> None:
"""
Get the signed app ticket for the current user.
Returns discordsdk.Enum.Result (int) and str via callback.
"""
def c_callback(callback_data, result, data):
self._garbage.remove(c_callback)
if result == Result.ok:
callback(result, data.contents.value.decode("utf8"))
else:
callback(result, None)
c_callback = self._internal.get_ticket.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.get_ticket(self._internal, ctypes.c_void_p(), c_callback)

View file

@ -0,0 +1,202 @@
import ctypes
import typing as t
from . import sdk
from .achievement import AchievementManager
from .activity import ActivityManager
from .application import ApplicationManager
from .enum import CreateFlags, LogLevel, Result
from .exception import get_exception
from .image import ImageManager
from .lobby import LobbyManager
from .network import NetworkManager
from .overlay import OverlayManager
from .relationship import RelationshipManager
from .storage import StorageManager
from .store import StoreManager
from .user import UserManager
from .voice import VoiceManager
class Discord:
_garbage: t.List[t.Any]
core: sdk.IDiscordCore = None
def __init__(self, client_id: int, flags: CreateFlags, dll_base_path: str):
self._garbage = []
self._activity_manager = ActivityManager()
self._relationship_manager = RelationshipManager()
self._image_manager = ImageManager()
self._user_manager = UserManager()
self._lobby_manager = LobbyManager()
self._network_manager = NetworkManager()
self._overlay_manager = OverlayManager()
self._application_manager = ApplicationManager()
self._storage_manager = StorageManager()
self._store_manager = StoreManager()
self._voice_manager = VoiceManager()
self._achievement_manager = AchievementManager()
version = sdk.DiscordVersion(2)
params = sdk.DiscordCreateParams()
params.client_id = client_id
params.flags = flags
sdk.DiscordCreateParamsSetDefault(params)
params.activity_events = self._activity_manager._events
params.relationship_events = self._relationship_manager._events
params.image_events = self._image_manager._events
params.user_events = self._user_manager._events
params.lobby_events = self._lobby_manager._events
params.network_events = self._network_manager._events
params.overlay_events = self._overlay_manager._events
params.application_events = self._application_manager._events
params.storage_events = self._storage_manager._events
params.store_events = self._store_manager._events
params.voice_events = self._voice_manager._events
params.achievement_events = self._achievement_manager._events
pointer = ctypes.POINTER(sdk.IDiscordCore)()
result = Result(sdk.build_sdk(dll_base_path)(version, ctypes.pointer(params), ctypes.pointer(pointer)))
if result != Result.ok:
raise get_exception(result)
self.core = pointer.contents
def __del__(self):
if self.core:
self.core.destroy(self.core)
self.core = None
def set_log_hook(self, min_level: LogLevel, hook: t.Callable[[LogLevel, str], None]) -> None:
"""
Registers a logging callback function with the minimum level of message to receive.
"""
def c_hook(hook_data, level, message):
level = LogLevel(level)
hook(level, message.decode("utf8"))
c_hook = self.core.set_log_hook.argtypes[-1](c_hook)
self._garbage.append(c_hook)
self.core.set_log_hook(self.core, min_level.value, ctypes.c_void_p(), c_hook)
def run_callbacks(self) -> None:
"""
Runs all pending SDK callbacks.
"""
result = Result(self.core.run_callbacks(self.core))
if result != Result.ok:
raise get_exception(result)
def get_activity_manager(self) -> ActivityManager:
"""
Fetches an instance of the manager for interfacing with activies in the SDK.
"""
if not self._activity_manager._internal:
self._activity_manager._internal = self.core.get_activity_manager(self.core).contents
return self._activity_manager
def get_relationship_manager(self) -> RelationshipManager:
"""
Fetches an instance of the manager for interfacing with relationships in the SDK.
"""
if not self._relationship_manager._internal:
self._relationship_manager._internal = self.core.get_relationship_manager(self.core).contents # noqa: E501
return self._relationship_manager
def get_image_manager(self) -> ImageManager:
"""
Fetches an instance of the manager for interfacing with images in the SDK.
"""
if not self._image_manager._internal:
self._image_manager._internal = self.core.get_image_manager(self.core).contents
return self._image_manager
def get_user_manager(self) -> UserManager:
"""
Fetches an instance of the manager for interfacing with users in the SDK.
"""
if not self._user_manager._internal:
self._user_manager._internal = self.core.get_user_manager(self.core).contents
return self._user_manager
def get_lobby_manager(self) -> LobbyManager:
"""
Fetches an instance of the manager for interfacing with lobbies in the SDK.
"""
if not self._lobby_manager._internal:
self._lobby_manager._internal = self.core.get_lobby_manager(self.core).contents
return self._lobby_manager
def get_network_manager(self) -> NetworkManager:
"""
Fetches an instance of the manager for interfacing with networking in the SDK.
"""
if not self._network_manager._internal:
self._network_manager._internal = self.core.get_network_manager(self.core).contents
return self._network_manager
def get_overlay_manager(self) -> OverlayManager:
"""
Fetches an instance of the manager for interfacing with the overlay in the SDK.
"""
if not self._overlay_manager._internal:
self._overlay_manager._internal = self.core.get_overlay_manager(self.core).contents
return self._overlay_manager
def get_application_manager(self) -> ApplicationManager:
"""
Fetches an instance of the manager for interfacing with applications in the SDK.
"""
if not self._application_manager._internal:
self._application_manager._internal = self.core.get_application_manager(self.core).contents # noqa: E501
return self._application_manager
def get_storage_manager(self) -> StorageManager:
"""
Fetches an instance of the manager for interfacing with storage in the SDK.
"""
if not self._storage_manager._internal:
self._storage_manager._internal = self.core.get_storage_manager(self.core).contents
return self._storage_manager
def get_store_manager(self) -> StoreManager:
"""
Fetches an instance of the manager for interfacing with SKUs and Entitlements in the SDK.
"""
if not self._store_manager._internal:
self._store_manager._internal = self.core.get_store_manager(self.core).contents
return self._store_manager
def get_voice_manager(self) -> VoiceManager:
"""
Fetches an instance of the manager for interfacing with voice chat in the SDK.
"""
if not self._voice_manager._internal:
self._voice_manager._internal = self.core.get_voice_manager(self.core).contents
return self._voice_manager
def get_achievement_manager(self) -> AchievementManager:
"""
Fetches an instance of the manager for interfacing with achievements in the SDK.
"""
if not self._achievement_manager._internal:
self._achievement_manager._internal = self.core.get_achievement_manager(self.core).contents # noqa: E501
return self._achievement_manager

View file

@ -0,0 +1,166 @@
import sys
if sys.version_info >= (3, 6):
from enum import IntEnum, IntFlag
else:
from enum import IntEnum, IntEnum as IntFlag
class Result(IntEnum):
ok = 0
service_unavailable = 1
invalid_version = 2
lock_failed = 3
internal_error = 4
invalid_payload = 5
invalid_command = 6
invalid_permissions = 7
not_fetched = 8
not_found = 9
conflict = 10
invalid_secret = 11
invalid_join_secret = 12
no_eligible_activity = 13
invalid_invite = 14
not_authenticated = 15
invalid_access_token = 16
application_mismatch = 17
invalid_data_url = 18
invalid_base_64 = 19
not_filtered = 20
lobby_full = 21
invalid_lobby_secret = 22
invalid_filename = 23
invalid_file_size = 24
invalid_entitlement = 25
not_installed = 26
not_running = 27
insufficient_buffer = 28
purchase_canceled = 29
invalid_guild = 30
invalid_event = 31
invalid_channel = 32
invalid_origin = 33
rate_limited = 34
oauth2_error = 35
select_channel_timeout = 36
get_guild_timeout = 37
select_voice_force_required = 38
capture_shortcut_already_listening = 39
unauthorized_for_achievement = 40
invalid_gift_code = 41
purchase_error = 42
transaction_aborted = 43
drawing_init_failed = 44
class LogLevel(IntEnum):
error = 0
warning = 1
info = 2
debug = 3
class CreateFlags(IntFlag):
default = 0
no_require_discord = 1
class UserFlag(IntFlag):
partner = 2
hype_squad_events = 4
hype_squad_house_1 = 64
hype_squad_house_2 = 128
hype_squad_house_3 = 256
class PremiumType(IntEnum):
none_ = 0
tier_1 = 1
tier_2 = 2
class ActivityType(IntEnum):
playing = 0
streaming = 1
listening = 2
custom = 4
class ActivityJoinRequestReply(IntEnum):
no = 0
yes = 1
ignore = 2
class ActivityActionType(IntEnum):
join = 1
spectate = 2
class RelationshipType(IntEnum):
none_ = 0
friend = 1
blocked = 2
pending_incoming = 3
pending_outgoing = 4
implicit = 5
class Status(IntEnum):
offline = 0
online = 1
idle = 2
do_not_disturb = 3
class ImageType(IntEnum):
user = 0
class LobbyType(IntEnum):
private = 1
public = 2
class LobbySearchComparison(IntEnum):
LessThanOrEqual = -2
LessThan = -1
Equal = 0
GreaterThan = 1
GreaterThanOrEqual = 2
NotEqual = 3
class LobbySearchCast(IntEnum):
String = 1
Number = 2
class LobbySearchDistance(IntEnum):
Local = 0
Default = 1
Extended = 2
Global = 3
class InputModeType(IntEnum):
VoiceActivity = 0
PushToTalk = 1
class SkuType(IntEnum):
Application = 1
DLC = 2
Consumable = 3
Bundle = 4
class EntitlementType(IntEnum):
Purchase = 1
PremiumSubscription = 2
DeveloperGift = 3
TestModePurchase = 4
FreePurchase = 5
UserGift = 6
PremiumPurchase = 7

View file

@ -0,0 +1,11 @@
import ctypes
import typing as t
def bind_events(structure: ctypes.Structure, *methods: t.List[t.Callable[..., None]]):
contents = structure()
for index, (name, func) in enumerate(structure._fields_):
setattr(contents, name, func(methods[index]))
pointer = ctypes.pointer(contents)
return pointer

View file

@ -0,0 +1,19 @@
from .enum import Result
class DiscordException(Exception):
pass
exceptions = {}
# we dynamically create the exceptions
for res in Result:
exception = type(res.name, (DiscordException,), {})
globals()[res.name] = exception
exceptions[res] = exception
def get_exception(result):
return exceptions.get(result, DiscordException)("result " + str(result.value))

View file

@ -0,0 +1,71 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .exception import get_exception
from .model import ImageDimensions, ImageHandle
class ImageManager:
_internal: sdk.IDiscordImageManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordImageEvents = None
def __init__(self):
self._garbage = []
def fetch(
self,
handle: ImageHandle,
refresh: bool,
callback: t.Callable[[Result, t.Optional[ImageHandle]], None]
) -> None:
"""
Prepares an image to later retrieve data about it.
Returns discordsdk.enum.Result (int) and ImageHandle via callback.
"""
def c_callback(callback_data, result, handle):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
callback(result, ImageHandle(internal=handle))
else:
callback(result, None)
c_callback = self._internal.fetch.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.fetch(
self._internal,
handle._internal,
refresh,
ctypes.c_void_p(),
c_callback
)
def get_dimensions(self, handle: ImageHandle) -> ImageDimensions:
"""
Gets the dimension for the given user's avatar's source image
"""
dimensions = sdk.DiscordImageDimensions()
result = Result(self._internal.get_dimensions(self._internal, handle._internal, dimensions))
if result != Result.ok:
raise get_exception(result)
return ImageDimensions(internal=dimensions)
def get_data(self, handle: ImageHandle) -> bytes:
"""
Gets the image data for a given user's avatar.
"""
dimensions = self.get_dimensions(handle)
buffer = (ctypes.c_uint8 * (dimensions.width * dimensions.height * 4))()
result = Result(self._internal.get_data(
self._internal, handle._internal, buffer, len(buffer)))
if result != Result.ok:
raise get_exception(result)
return bytes(buffer)

View file

@ -0,0 +1,801 @@
import ctypes
import typing as t
from . import sdk
from .enum import (
LobbySearchCast, LobbySearchComparison, LobbySearchDistance, LobbyType,
Result)
from .event import bind_events
from .exception import get_exception
from .model import Lobby, User
class LobbyTransaction:
_internal: sdk.IDiscordLobbyTransaction
def __init__(self, internal: sdk.IDiscordLobbyTransaction):
self._internal = internal
def set_type(self, type: LobbyType) -> None:
"""
Marks a lobby as private or public.
"""
result = Result(self._internal.set_type(self._internal, type))
if result != Result.ok:
raise get_exception(result)
def set_owner(self, user_id: int) -> None:
"""
Sets a new owner for the lobby.
"""
result = Result(self._internal.set_owner(self._internal, user_id))
if result != Result.ok:
raise get_exception(result)
def set_capacity(self, capacity: int) -> None:
"""
Sets a new capacity for the lobby.
"""
result = Result(self._internal.set_capacity(self._internal, capacity))
if result != Result.ok:
raise get_exception(result)
def set_metadata(self, key: str, value: str) -> None:
"""
Sets metadata value under a given key name for the lobby.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
metadata_value.value = value.encode("utf8")
result = Result(self._internal.set_metadata(self._internal, metadata_key, metadata_value))
if result != Result.ok:
raise get_exception(result)
def delete_metadata(self, key: str) -> None:
"""
Deletes the lobby metadata for a key.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
result = Result(self._internal.delete_metadata(self._internal, metadata_key))
if result != Result.ok:
raise get_exception(result)
def set_locked(self, locked: bool) -> None:
"""
Sets the lobby to locked or unlocked.
"""
result = Result(self._internal.set_locked(self._internal, locked))
if result != Result.ok:
raise get_exception(result)
class LobbyMemberTransaction:
_internal: sdk.IDiscordLobbyMemberTransaction
def __init__(self, internal: sdk.IDiscordLobbyMemberTransaction):
self._internal = internal
def set_metadata(self, key: str, value: str) -> None:
"""
Sets metadata value under a given key name for the current user.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
metadata_value.value = value.encode("utf8")
result = Result(self._internal.set_metadata(self._internal, metadata_key, metadata_value))
if result != Result.ok:
raise get_exception(result)
def delete_metadata(self, key: str) -> None:
"""
Sets metadata value under a given key name for the current user.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
result = Result(self._internal.delete_metadata(self._internal, metadata_key))
if result != Result.ok:
raise get_exception(result)
class LobbySearchQuery:
_internal: sdk.IDiscordLobbySearchQuery
def __init__(self, internal: sdk.IDiscordLobbySearchQuery):
self._internal = internal
def filter(
self,
key: str,
comp: LobbySearchComparison,
cast: LobbySearchCast,
value: str
) -> None:
"""
Filters lobbies based on metadata comparison.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
metadata_value.value = value.encode("utf8")
result = Result(self._internal.filter(
self._internal,
metadata_key,
comp,
cast,
metadata_value
))
if result != Result.ok:
raise get_exception(result)
def sort(self, key: str, cast: LobbySearchCast, value: str) -> None:
"""
Sorts the filtered lobbies based on "near-ness" to a given value.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
metadata_value.value = value.encode("utf8")
result = Result(self._internal.sort(self._internal, metadata_key, cast, metadata_value))
if result != Result.ok:
raise get_exception(result)
def limit(self, limit: int) -> None:
"""
Limits the number of lobbies returned in a search.
"""
result = Result(self._internal.limit(self._internal, limit))
if result != Result.ok:
raise get_exception(result)
def distance(self, distance: LobbySearchDistance) -> None:
"""
Filters lobby results to within certain regions relative to the user's location.
"""
result = Result(self._internal.distance(self._internal, distance))
if result != Result.ok:
raise get_exception(result)
class LobbyManager:
_internal: sdk.IDiscordLobbyManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordLobbyEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordLobbyEvents,
self._on_lobby_update,
self._on_lobby_delete,
self._on_member_connect,
self._on_member_update,
self._on_member_disconnect,
self._on_lobby_message,
self._on_speaking,
self._on_network_message
)
def _on_lobby_update(self, event_data, lobby_id):
self.on_lobby_update(lobby_id)
def _on_lobby_delete(self, event_data, lobby_id, reason):
self.on_lobby_delete(lobby_id, reason)
def _on_member_connect(self, event_data, lobby_id, user_id):
self.on_member_connect(lobby_id, user_id)
def _on_member_update(self, event_data, lobby_id, user_id):
self.on_member_update(lobby_id, user_id)
def _on_member_disconnect(self, event_data, lobby_id, user_id):
self.on_member_disconnect(lobby_id, user_id)
def _on_lobby_message(self, event_data, lobby_id, user_id, data, data_length):
message = bytes(data[:data_length]).decode("utf8")
self.on_lobby_message(lobby_id, user_id, message)
def _on_speaking(self, event_data, lobby_id, user_id, speaking):
self.on_speaking(lobby_id, user_id, speaking)
def _on_network_message(self, event_data, lobby_id, user_id, channel_id, data, data_length):
data = bytes(data[:data_length])
self.on_network_message(lobby_id, user_id, channel_id, data)
def get_lobby_create_transaction(self) -> LobbyTransaction:
"""
Gets a Lobby transaction used for creating a new lobby
"""
transaction = ctypes.POINTER(sdk.IDiscordLobbyTransaction)()
result = Result(self._internal.get_lobby_create_transaction(self._internal, transaction))
if result != Result.ok:
raise get_exception(result)
return LobbyTransaction(internal=transaction.contents)
def get_lobby_update_transaction(self, lobby_id: int) -> LobbyTransaction:
"""
Gets a lobby transaction used for updating an existing lobby.
"""
transaction = ctypes.POINTER(sdk.IDiscordLobbyTransaction)()
result = Result(self._internal.get_lobby_update_transaction(
self._internal,
lobby_id,
transaction
))
if result != Result.ok:
raise get_exception(result)
return LobbyTransaction(internal=transaction.contents)
def get_member_update_transaction(self, lobby_id: int, user_id: int) -> LobbyMemberTransaction:
"""
Gets a new member transaction for a lobby member in a given lobby.
"""
transaction = ctypes.POINTER(sdk.IDiscordLobbyMemberTransaction)()
result = Result(self._internal.get_member_update_transaction(
self._internal, lobby_id, user_id, transaction))
if result != Result.ok:
raise get_exception(result)
return LobbyMemberTransaction(internal=transaction.contents)
def create_lobby(
self,
transaction: LobbyTransaction,
callback: t.Callable[[Result, t.Optional[Lobby]], None]
) -> None:
"""
Creates a lobby.
Returns discordsdk.enum.Result (int) and Lobby via callback.
"""
def c_callback(callback_data, result, lobby):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
callback(result, Lobby(copy=lobby.contents))
else:
callback(result, None)
c_callback = self._internal.create_lobby.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.create_lobby(
self._internal,
transaction._internal,
ctypes.c_void_p(),
c_callback
)
def update_lobby(
self,
lobby_id: int,
transaction: LobbyTransaction,
callback: t.Callable[[Result], None]
) -> None:
"""
Updates a lobby with data from the given transaction.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.update_lobby.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.update_lobby(
self._internal,
lobby_id,
transaction._internal,
ctypes.c_void_p(),
c_callback
)
def delete_lobby(self, lobby_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Deletes a given lobby.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.delete_lobby.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.delete_lobby(self._internal, lobby_id, ctypes.c_void_p(), c_callback)
def connect_lobby(
self,
lobby_id: int,
lobby_secret: str,
callback: t.Callable[[Result], None]
) -> None:
"""
Connects the current user to a given lobby.
"""
def c_callback(callback_data, result, lobby):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
callback(result, Lobby(copy=lobby.contents))
else:
callback(result, None)
c_callback = self._internal.connect_lobby.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
_lobby_secret = sdk.DiscordLobbySecret()
_lobby_secret.value = lobby_secret.encode("utf8")
self._internal.connect_lobby(
self._internal,
lobby_id,
_lobby_secret,
ctypes.c_void_p(),
c_callback
)
def connect_lobby_with_activity_secret(
self,
activity_secret: str,
callback: t.Callable[[Result, t.Optional[Lobby]], None]
) -> None:
"""
Connects the current user to a lobby; requires the special activity secret from the lobby
which is a concatenated lobby_id and secret.
"""
def c_callback(callback_data, result, lobby):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
callback(result, Lobby(copy=lobby.contents))
else:
callback(result, None)
c_callback = self._internal.connect_lobby_with_activity_secret.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
_activity_secret = sdk.DiscordLobbySecret()
_activity_secret.value = activity_secret.encode("utf8")
self._internal.connect_lobby_with_activity_secret(
self._internal,
_activity_secret,
ctypes.c_void_p(),
c_callback
)
def get_lobby_activity_secret(self, lobby_id: int) -> str:
"""
Gets the special activity secret for a given lobby.
"""
lobby_secret = sdk.DiscordLobbySecret()
result = self._internal.get_lobby_activity_secret(self._internal, lobby_id, lobby_secret)
if result != Result.ok:
raise get_exception(result)
return lobby_secret.value.decode("utf8")
def disconnect_lobby(self, lobby_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Disconnects the current user from a lobby.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.disconnect_lobby.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.disconnect_lobby(self._internal, lobby_id, ctypes.c_void_p(), c_callback)
def get_lobby(self, lobby_id: int) -> Lobby:
"""
Gets the lobby object for a given lobby id.
"""
lobby = sdk.DiscordLobby()
result = Result(self._internal.get_lobby(self._internal, lobby_id, lobby))
if result != Result.ok:
raise get_exception(result)
return Lobby(internal=lobby)
def lobby_metadata_count(self, lobby_id: int) -> int:
"""
Returns the number of metadata key/value pairs on a given lobby.
"""
count = ctypes.c_int32()
result = Result(self._internal.lobby_metadata_count(self._internal, lobby_id, count))
if result != Result.ok:
raise get_exception(result)
return count.value
def get_lobby_metadata_key(self, lobby_id: int, index: int) -> str:
"""
Returns the key for the lobby metadata at the given index.
"""
metadata_key = sdk.DiscordMetadataKey()
result = Result(self._internal.get_lobby_metadata_key(
self._internal,
lobby_id,
index,
metadata_key
))
if result != Result.ok:
raise get_exception(result)
return metadata_key.value.decode("utf8")
def get_lobby_metadata_value(self, lobby_id: int, key: str) -> str:
"""
Returns lobby metadata value for a given key and id.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
result = Result(self._internal.get_lobby_metadata_value(
self._internal,
lobby_id,
metadata_key,
metadata_value
))
if result != Result.ok:
raise get_exception(result)
return metadata_value.value.decode("utf8")
def member_count(self, lobby_id: int) -> int:
"""
Get the number of members in a lobby.
"""
count = ctypes.c_int32()
result = Result(self._internal.member_count(self._internal, lobby_id, count))
if result != Result.ok:
raise get_exception(result)
return count.value
def get_member_user_id(self, lobby_id: int, index: int) -> int:
"""
Gets the user id of the lobby member at the given index.
"""
user_id = sdk.DiscordUserId()
result = Result(self._internal.get_member_user_id(self._internal, lobby_id, index, user_id))
if result != Result.ok:
raise get_exception(result)
return user_id.value
def get_member_user(self, lobby_id: int, user_id: int) -> User:
"""
Gets the user object for a given user id.
"""
user = sdk.DiscordUser()
result = Result(self._internal.get_member_user(self._internal, lobby_id, user_id, user))
if result != Result.ok:
raise get_exception(result)
return User(internal=user)
def member_metadata_count(self, lobby_id: int, user_id: int) -> int:
"""
Gets the number of metadata key/value pairs for the given lobby member.
"""
count = ctypes.c_int32()
result = Result(self._internal.member_metadata_count(
self._internal,
lobby_id,
user_id,
count
))
if result != Result.ok:
raise get_exception(result)
return count.value
def get_member_metadata_key(self, lobby_id: int, user_id: int, index: int) -> str:
"""
Gets the key for the lobby metadata at the given index on a lobby member.
"""
metadata_key = sdk.DiscordMetadataKey()
result = Result(self._internal.get_member_metadata_key(
self._internal,
lobby_id,
user_id,
index,
metadata_key
))
if result != Result.ok:
raise get_exception(result)
return metadata_key.value.decode("utf8")
def get_member_metadata_value(self, lobby_id: int, user_id: int, key: str) -> str:
"""
Returns user metadata for a given key.
"""
metadata_key = sdk.DiscordMetadataKey()
metadata_key.value = key.encode("utf8")
metadata_value = sdk.DiscordMetadataValue()
result = Result(self._internal.get_member_metadata_value(
self._internal,
lobby_id,
user_id,
metadata_key,
metadata_value
))
if result != Result.ok:
raise get_exception(result)
return metadata_value.value.decode("utf8")
def update_member(
self,
lobby_id: int,
user_id: int,
transaction: LobbyMemberTransaction,
callback: t.Callable[[Result], None]
) -> None:
"""
Updates lobby member info for a given member of the lobby.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.update_member.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.update_member(
self._internal,
lobby_id,
user_id,
transaction._internal,
ctypes.c_void_p(),
c_callback
)
def send_lobby_message(
self,
lobby_id: int,
data: str,
callback: t.Callable[[Result], None]
) -> None:
"""
Sends a message to the lobby on behalf of the current user.
Returns discordsdk.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.send_lobby_message.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
data = data.encode("utf8")
data = (ctypes.c_uint8 * len(data))(*data)
self._internal.send_lobby_message(
self._internal, lobby_id, data, len(data), ctypes.c_void_p(), c_callback)
def get_search_query(self) -> LobbySearchQuery:
"""
Creates a search object to search available lobbies.
"""
search_query = (ctypes.POINTER(sdk.IDiscordLobbySearchQuery))()
result = Result(self._internal.get_search_query(self._internal, ctypes.byref(search_query)))
if result != Result.ok:
raise get_exception(result)
return LobbySearchQuery(internal=search_query.contents)
def search(self, search: LobbySearchQuery, callback: t.Callable[[Result], None]) -> None:
"""
Searches available lobbies based on the search criteria chosen in the LobbySearchQuery
member functions.
Lobbies that meet the criteria are then globally filtered, and can be accessed via
iteration with lobby_count() and get_lobby_id(). The callback fires when the list of lobbies
is stable and ready for iteration.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.search.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.search(self._internal, search._internal, ctypes.c_void_p(), c_callback)
def lobby_count(self) -> int:
"""
Get the number of lobbies that match the search.
"""
count = ctypes.c_int32()
self._internal.lobby_count(self._internal, count)
return count.value
def get_lobby_id(self, index: int) -> int:
"""
Returns the id for the lobby at the given index.
"""
lobby_id = sdk.DiscordLobbyId()
result = Result(self._internal.get_lobby_id(self._internal, index, lobby_id))
if result != Result.ok:
raise get_exception(result)
return lobby_id.value
def connect_voice(self, lobby_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Connects to the voice channel of the current lobby.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.connect_voice.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.connect_voice(self._internal, lobby_id, ctypes.c_void_p(), c_callback)
def disconnect_voice(self, lobby_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Disconnects from the voice channel of a given lobby.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.disconnect_voice.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.disconnect_voice(self._internal, lobby_id, ctypes.c_void_p(), c_callback)
def on_lobby_update(self, lobby_id: int) -> None:
"""
Fires when a lobby is updated.
"""
def on_lobby_delete(self, lobby_id: int, reason: str) -> None:
"""
Fired when a lobby is deleted.
"""
def on_member_connect(self, lobby_id: int, user_id: int) -> None:
"""
Fires when a new member joins the lobby.
"""
def on_member_update(self, lobby_id: int, user_id: int) -> None:
"""
Fires when data for a lobby member is updated.
"""
def on_member_disconnect(self, lobby_id: int, user_id: int) -> None:
"""
Fires when a member leaves the lobby.
"""
def on_lobby_message(self, lobby_id: int, user_id: int, message: str) -> None:
"""
Fires when a message is sent to the lobby.
"""
def on_speaking(self, lobby_id: int, user_id: int, speaking: bool) -> None:
"""
Fires when a user connected to voice starts or stops speaking.
"""
def connect_network(self, lobby_id: int) -> None:
"""
Connects to the networking layer for the given lobby ID.
"""
result = Result(self._internal.connect_network(self._internal, lobby_id))
if result != Result.ok:
raise get_exception(result)
def disconnect_network(self, lobby_id: int) -> None:
"""
Disconnects from the networking layer for the given lobby ID.
"""
result = Result(self._internal.disconnect_network(self._internal, lobby_id))
if result != Result.ok:
raise get_exception(result)
def flush_network(self) -> None:
"""
Flushes the network. Call this when you're done sending messages.
"""
result = Result(self._internal.flush_network(self._internal))
if result != Result.ok:
raise get_exception(result)
def open_network_channel(self, lobby_id: int, channel_id: int, reliable: bool) -> None:
"""
Opens a network channel to all users in a lobby on the given channel number. No need to
iterate over everyone!
"""
result = Result(self._internal.open_network_channel(
self._internal,
lobby_id,
channel_id,
reliable
))
if result != Result.ok:
raise get_exception(result)
def send_network_message(
self,
lobby_id: int,
user_id: int,
channel_id: int,
data: bytes
) -> None:
"""
Sends a network message to the given user ID that is a member of the given lobby ID over
the given channel ID.
"""
data = (ctypes.c_uint8 * len(data))(*data)
result = Result(self._internal.send_network_message(
self._internal,
lobby_id,
user_id,
channel_id,
data,
len(data)
))
if result != Result.ok:
raise get_exception(result)
def on_network_message(self, lobby_id: int, user_id: int, channel_id: int, data: bytes) -> None:
"""
Fires when the user receives a message from the lobby's networking layer.
"""

View file

@ -0,0 +1,329 @@
import ctypes
from enum import Enum
from . import sdk
from .enum import (EntitlementType, ImageType, InputModeType, LobbyType,
RelationshipType, SkuType, Status)
class Model:
def __init__(self, **kwargs):
self._internal = kwargs.get("internal", self._struct_())
if "copy" in kwargs:
ctypes.memmove(
ctypes.byref(self._internal),
ctypes.byref(kwargs["copy"]),
ctypes.sizeof(self._struct_)
)
self._fields = {}
for field, ftype in self._fields_:
self._fields[field] = ftype
if issubclass(ftype, Model):
setattr(self, "_" + field, ftype(internal=getattr(self._internal, field)))
def __getattribute__(self, key):
if key.startswith("_"):
return super().__getattribute__(key)
else:
ftype = self._fields[key]
value = getattr(self._internal, key)
if ftype == int:
return int(value)
elif ftype == str:
return value.decode("utf8")
elif ftype == bool:
return bool(value)
elif issubclass(ftype, Model):
return getattr(self, "_" + key)
elif issubclass(ftype, Enum):
return ftype(int(value))
else:
raise TypeError(ftype)
def __setattr__(self, key, value):
if key.startswith("_"):
super().__setattr__(key, value)
else:
ftype = self._fields[key]
if ftype == int:
value = int(value)
setattr(self._internal, key, value)
elif ftype == str:
value = value.encode("utf8")
setattr(self._internal, key, value)
elif ftype == bool:
value = bool(value)
setattr(self._internal, key, value)
elif issubclass(ftype, Model):
setattr(self, "_" + key, value)
setattr(self._internal, key, value._internal)
elif issubclass(ftype, Enum):
setattr(self._internal, key, value.value)
else:
raise TypeError(ftype)
def __dir__(self):
return super().__dir__() + list(self._fields.keys())
class User(Model):
_struct_ = sdk.DiscordUser
_fields_ = [
("id", int),
("username", str),
("discriminator", str),
("avatar", str),
("bot", bool),
]
id: int
username: str
discriminator: str
avatar: str
bot: str
class ActivityTimestamps(Model):
_struct_ = sdk.DiscordActivityTimestamps
_fields_ = [
("start", int),
("end", int),
]
start: int
end: int
class ActivityAssets(Model):
_struct_ = sdk.DiscordActivityAssets
_fields_ = [
("large_image", str),
("large_text", str),
("small_image", str),
("small_text", str),
]
large_image: str
large_text: str
small_image: str
small_text: str
class PartySize(Model):
_struct_ = sdk.DiscordPartySize
_fields_ = [
("current_size", int),
("max_size", int),
]
current_size: int
max_size: int
class ActivityParty(Model):
_struct_ = sdk.DiscordActivityParty
_fields_ = [
("id", str),
("size", PartySize),
]
id: str
size: PartySize
class ActivitySecrets(Model):
_struct_ = sdk.DiscordActivitySecrets
_fields_ = [
("match", str),
("join", str),
("spectate", str),
]
match: str
join: str
spectate: str
class Activity(Model):
_struct_ = sdk.DiscordActivity
_fields_ = [
("application_id", int),
("name", str),
("state", str),
("details", str),
("timestamps", ActivityTimestamps),
("assets", ActivityAssets),
("party", ActivityParty),
("secrets", ActivitySecrets),
("instance", bool),
]
application_id: int
name: str
state: str
details: str
timestamps: ActivityTimestamps
assets: ActivityAssets
party: ActivityParty
secrets: ActivitySecrets
instance: bool
class Presence(Model):
_struct_ = sdk.DiscordPresence
_fields_ = [
("status", Status),
("activity", Activity),
]
status: Status
activity: Activity
class Relationship(Model):
_struct_ = sdk.DiscordRelationship
_fields_ = [
("type", RelationshipType),
("user", User),
("presence", Presence),
]
type: RelationshipType
user: User
presence: Presence
class ImageDimensions(Model):
_struct_ = sdk.DiscordImageDimensions
_fields_ = [
("width", int),
("height", int),
]
width: int
height: int
class ImageHandle(Model):
_struct_ = sdk.DiscordImageHandle
_fields_ = [
("type", ImageType),
("id", int),
("size", int),
]
type: ImageType
id: int
size: int
class OAuth2Token(Model):
_struct_ = sdk.DiscordOAuth2Token
_fields_ = [
("access_token", str),
("scopes", str),
("expires", int),
]
access_token: str
scopes: str
expires: str
class Lobby(Model):
_struct_ = sdk.DiscordLobby
_fields_ = [
("id", int),
("type", LobbyType),
("owner_id", int),
("secret", str),
("capacity", int),
("locked", bool),
]
id: int
type: LobbyType
owner_id: int
secret: str
capacity: int
locked: bool
class InputMode(Model):
_struct_ = sdk.DiscordInputMode
_fields_ = [
("type", InputModeType),
("shortcut", str),
]
type: InputModeType
shortcut: str
class FileStat(Model):
_struct_ = sdk.DiscordFileStat
_fields_ = [
("filename", str),
("size", int),
("last_modified", int),
]
filename: str
size: int
last_modified: int
class UserAchievement(Model):
_struct_ = sdk.DiscordUserAchievement
_fields_ = [
("user_id", str),
("achievement_id", int),
("percent_complete", int),
("unlocked_at", str),
]
user_id: str
achievement_id: int
percent_complete: int
unlocked_at: str
class SkuPrice(Model):
_struct_ = sdk.DiscordSkuPrice
_fields_ = [
("amount", int),
("currency", str),
]
amount: int
currency: str
class Sku(Model):
_struct_ = sdk.DiscordSku
_fields_ = [
("id", int),
("type", SkuType),
("name", str),
("price", SkuPrice),
]
id: int
type: SkuType
name: str
price: SkuPrice
class Entitlement(Model):
_struct_ = sdk.DiscordEntitlement
_fields_ = [
("id", int),
("type", EntitlementType),
("sku_id", int),
]
id: int
type: EntitlementType
sku_id: int

View file

@ -0,0 +1,111 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .event import bind_events
from .exception import get_exception
class NetworkManager:
_internal: sdk.IDiscordNetworkManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordNetworkEvents
def __init__(self):
self._events = bind_events(
sdk.IDiscordNetworkEvents,
self._on_message,
self._on_route_update
)
def _on_message(self, event_data, peer_id, channel_id, data, data_length):
data = bytes(data[:data_length])
self.on_message(peer_id, channel_id, data)
def _on_route_update(self, event_data, route_data):
self.on_route_update(route_data.decode("utf8"))
def get_peer_id(self) -> int:
"""
Get the networking peer_id for the current user, allowing other users to send packets to
them.
"""
peerId = sdk.DiscordNetworkPeerId()
self._internal.get_peer_id(self._internal, peerId)
return peerId.value
def flush(self) -> None:
"""
Flushes the network.
"""
result = Result(self._internal.flush(self._internal))
if result != Result.ok:
raise get_exception(result)
def open_channel(self, peer_id: int, channel_id: int, reliable: bool) -> None:
"""
Opens a channel to a user with their given peer_id on the given channel number.
"""
result = Result(self._internal.open_channel(self._internal, peer_id, channel_id, reliable))
if result != Result.ok:
raise get_exception(result)
def open_peer(self, peer_id: int, route: str) -> None:
"""
Opens a network connection to another Discord user.
"""
route_data = ctypes.create_string_buffer(route.encode("utf8"))
result = Result(self._internal.open_peer(self._internal, peer_id, route_data))
if result != Result.ok:
raise get_exception(result)
def update_peer(self, peer_id: int, route: str) -> None:
"""
Updates the network connection to another Discord user.
"""
route_data = ctypes.create_string_buffer(route.encode("utf8"))
result = Result(self._internal.update_peer(self._internal, peer_id, route_data))
if result != Result.ok:
raise get_exception(result)
def send_message(self, peer_id: int, channel_id: int, data: bytes) -> None:
"""
Sends data to a given peer_id through the given channel.
"""
data = (ctypes.c_uint8 * len(data))(*data)
result = Result(self._internal.send_message(
self._internal,
peer_id,
channel_id,
data,
len(data)
))
if result != Result.ok:
raise get_exception(result)
def close_channel(self, peer_id: int, channel_id: int) -> None:
"""
Close the connection to a given user by peer_id on the given channel.
"""
result = Result(self._internal.close_channel(self._internal, peer_id, channel_id))
if result != Result.ok:
raise get_exception(result)
def close_peer(self, peer_id: int) -> None:
"""
Disconnects the network session to another Discord user.
"""
result = Result(self._internal.close_peer(self._internal, peer_id))
if result != Result.ok:
raise get_exception(result)
def on_message(self, peer_id: int, channel_id: int, data: bytes) -> None:
"""
Fires when you receive data from another user.
"""
def on_route_update(self, route: str) -> None:
"""
Fires when your networking route has changed.
"""

View file

@ -0,0 +1,104 @@
import ctypes
import typing as t
from . import sdk
from .enum import ActivityActionType, Result
from .event import bind_events
class OverlayManager:
_internal: sdk.IDiscordOverlayManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordOverlayEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordOverlayEvents,
self._on_toggle
)
def _on_toggle(self, event_data, locked):
self.on_toggle(locked)
def is_enabled(self) -> bool:
"""
Check whether the user has the overlay enabled or disabled.
"""
enabled = ctypes.c_bool()
self._internal.is_enabled(self._internal, enabled)
return enabled.value
def is_locked(self) -> bool:
"""
Check if the overlay is currently locked or unlocked
"""
locked = ctypes.c_bool()
self._internal.is_locked(self._internal, locked)
return locked.value
def set_locked(self, locked: bool, callback: t.Callable[[Result], None]) -> None:
"""
Locks or unlocks input in the overlay.
"""
def c_callback(event_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.set_locked.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.set_locked(self._internal, locked, ctypes.c_void_p(), c_callback)
def open_activity_invite(
self,
type: ActivityActionType,
callback: t.Callable[[Result], None]
) -> None:
"""
Opens the overlay modal for sending game invitations to users, channels, and servers.
"""
def c_callback(event_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.open_activity_invite.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.open_activity_invite(self._internal, type, ctypes.c_void_p(), c_callback)
def open_guild_invite(self, code: str, callback: t.Callable[[Result], None]) -> None:
"""
Opens the overlay modal for joining a Discord guild, given its invite code.
"""
def c_callback(event_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.open_guild_invite.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
code = ctypes.c_char_p(code.encode("utf8"))
self._internal.open_guild_invite(self._internal, code, ctypes.c_void_p(), c_callback)
def open_voice_settings(self, callback: t.Callable[[Result], None]) -> None:
"""
Opens the overlay widget for voice settings for the currently connected application.
"""
def c_callback(event_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.open_voice_settings.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.open_voice_settings(self._internal, ctypes.c_void_p(), c_callback)
def on_toggle(self, locked: bool) -> None:
"""
Fires when the overlay is locked or unlocked (a.k.a. opened or closed)
"""

View file

@ -0,0 +1,84 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .event import bind_events
from .exception import get_exception
from .model import Relationship
class RelationshipManager:
_internal: sdk.IDiscordRelationshipManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordRelationshipEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordRelationshipEvents,
self._on_refresh,
self._on_relationship_update
)
def _on_refresh(self, event_data):
self.on_refresh()
def _on_relationship_update(self, event_data, relationship):
self.on_relationship_update(Relationship(copy=relationship.contents))
def filter(self, filter: t.Callable[[Relationship], None]) -> None:
"""
Filters a user's relationship list by a boolean condition.
"""
def c_filter(filter_data, relationship):
return bool(filter(Relationship(copy=relationship.contents)))
c_filter = self._internal.filter.argtypes[-1](c_filter)
self._internal.filter(self._internal, ctypes.c_void_p(), c_filter)
def get(self, user_id: int) -> Relationship:
"""
Get the relationship between the current user and a given user by id.
"""
pointer = sdk.DiscordRelationship()
result = Result(self._internal.get(self._internal, user_id, pointer))
if result != Result.ok:
raise get_exception(result)
return Relationship(internal=pointer)
def get_at(self, index: int) -> Relationship:
"""
Get the relationship at a given index when iterating over a list of relationships.
"""
pointer = sdk.DiscordRelationship()
result = Result(self._internal.get_at(self._internal, index, pointer))
if result != Result.ok:
raise get_exception(result)
return Relationship(internal=pointer)
def count(self) -> int:
"""
Get the number of relationships that match your filter.
"""
count = ctypes.c_int32()
result = Result(self._internal.count(self._internal, count))
if result != Result.ok:
raise get_exception(result)
return count.value
def on_refresh(self) -> None:
"""
Fires at initialization when Discord has cached a snapshot of the current status of all
your relationships.
"""
def on_relationship_update(self, relationship: Relationship) -> None:
"""
Fires when a relationship in the filtered list changes, like an updated presence or user
attribute.
"""

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .exception import get_exception
from .model import FileStat
class StorageManager:
_internal: sdk.IDiscordStorageManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordStorageEvents = None
def __init__(self):
self._garbage = []
def get_path(self) -> str:
"""
Returns the filepath to which Discord saves files if you were to use the SDK's storage
manager.
"""
path = sdk.DiscordPath()
result = Result(self._internal.get_path(self._internal, path))
if result != Result.ok:
raise get_exception(result)
return path.value.decode("utf8")
def read(self, name: str) -> bytes:
"""
Reads data synchronously from the game's allocated save file.
"""
# we need the file stat for this one, as length-fixed buffers does not exist in python
file_stat = self.stat(name)
file_size = file_stat.Size
name = ctypes.c_char_p(name.encode("utf8"))
buffer = (ctypes.c_uint8 * file_size)()
read = ctypes.c_uint32()
result = Result(self._internal.read(self._internal, name, buffer, len(buffer), read))
if result != Result.ok:
raise get_exception(result)
if read.value != file_size:
print("discord/storage.py: warning: attempting to read " +
str(file_size) + " bytes, but read " + str(read.value))
return bytes(buffer[:read.value])
def read_async(
self,
name: str,
callback: t.Callable[[Result, t.Optional[bytes]], None]
) -> None:
"""
Reads data asynchronously from the game's allocated save file.
Returns discordsdk.enum.Result (int) and data (bytes) via callback.
"""
def c_callback(callback_data, result, data, data_length):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
data = bytes(data[:data_length])
callback(result, data)
else:
callback(result, None)
c_callback = self._internal.read_async.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
name = ctypes.c_char_p(name.encode("utf8"))
self._internal.read_async(self._internal, name, ctypes.c_void_p(), c_callback)
def read_async_partial(
self,
name: str,
offset: int,
length: int,
callback: t.Callable[[Result], None]
) -> None:
"""
Reads data asynchronously from the game's allocated save file, starting at a given offset
and up to a given length.
"""
def c_callback(callback_data, result, data, data_length):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
data = bytes(data[:data_length])
callback(result, data)
else:
callback(result, None)
c_callback = self._internal.read_async.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
name = ctypes.c_char_p(name.encode("utf8"))
self._internal.read_async_partial(
self._internal,
name,
offset,
length,
ctypes.c_void_p(),
c_callback
)
def write(self, name: str, data: bytes) -> None:
"""
Writes data synchronously to disk, under the given key name.
"""
name = ctypes.c_char_p(name.encode("utf8"))
data = (ctypes.c_uint8 * len(data))(*data)
result = Result(self._internal.write(self._internal, name, data, len(data)))
if result != Result.ok:
raise get_exception(result)
def write_async(self, name: str, data: bytes, callback: t.Callable[[Result], None]) -> None:
"""
Writes data asynchronously to disk under the given keyname.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.write_async.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
name = ctypes.c_char_p(name.encode("utf8"))
data = (ctypes.c_uint8 * len(data))(*data)
self._internal.write_async(
self._internal,
name,
data,
len(data),
ctypes.c_void_p(),
c_callback
)
def delete(self, name: str) -> None:
"""
Deletes written data for the given key name.
"""
name = ctypes.c_char_p(name.encode("utf8"))
result = Result(self._internal.delete_(self._internal, name))
if result != Result.ok:
raise get_exception(result)
def exists(self, name: str) -> bool:
"""
Checks if data exists for a given key name.
"""
exists = ctypes.c_bool()
name = ctypes.c_char_p(name.encode("utf8"))
result = Result(self._internal.exists(self._internal, name, exists))
if result != Result.ok:
raise get_exception(result)
return exists.value
def stat(self, name: str) -> FileStat:
"""
Returns file info for the given key name.
"""
stat = sdk.DiscordFileStat()
name = ctypes.c_char_p(name.encode("utf8"))
result = Result(self._internal.stat(self._internal, name, stat))
if result != Result.ok:
raise get_exception(result)
return FileStat(internal=stat)
def count(self) -> int:
"""
Returns the count of files, for iteration.
"""
count = ctypes.c_int32()
self._internal.count(self._internal, count)
return count.value
def stat_at(self, index: int) -> FileStat:
"""
Returns file info for the given index when iterating over files.
"""
stat = sdk.DiscordFileStat()
result = Result(self._internal.stat_at(self._internal, index, stat))
if result != Result.ok:
raise get_exception(result)
return FileStat(internal=stat)

View file

@ -0,0 +1,164 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .event import bind_events
from .exception import get_exception
from .model import Entitlement, Sku
class StoreManager:
_internal: sdk.IDiscordStoreManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordStoreEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordStoreEvents,
self._on_entitlement_create,
self._on_entitlement_delete
)
def _on_entitlement_create(self, event_data, entitlement):
self.on_entitlement_create(Entitlement(copy=entitlement))
def _on_entitlement_delete(self, event_data, entitlement):
self.on_entitlement_delete(Entitlement(copy=entitlement))
def fetch_skus(self, callback: t.Callable[[Result], None]) -> None:
"""
Fetches the list of SKUs for the connected application, readying them for iteration.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.fetch_skus.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.fetch_skus(self._internal, ctypes.c_void_p(), c_callback)
def count_skus(self) -> int:
"""
Get the number of SKUs readied by FetchSkus().
"""
count = ctypes.c_int32()
self._internal.count_skus(self._internal, count)
return count.value
def get_sku(self, sku_id: int) -> Sku:
"""
Gets a SKU by its ID.
"""
sku = sdk.DiscordSku()
result = Result(self._internal.get_sku(sku_id, sku))
if result != Result.ok:
raise get_exception(result)
return Sku(internal=sku)
def get_sku_at(self, index: int) -> Sku:
"""
Gets a SKU by index when iterating over SKUs.
"""
sku = sdk.DiscordSku()
result = Result(self._internal.get_sku_at(index, sku))
if result != Result.ok:
raise get_exception(result)
return Sku(internal=sku)
def fetch_entitlements(self, callback: t.Callable[[Result], None]) -> None:
"""
Fetches a list of entitlements to which the user is entitled.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.fetch_entitlements.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.fetch_entitlements(self._internal, ctypes.c_void_p(), c_callback)
def count_entitlements(self) -> int:
"""
Get the number of entitlements readied by FetchEntitlements().
"""
count = ctypes.c_int32()
self._internal.count_entitlements(self._internal, count)
return count.value
def get_entitlement(self, entitlement_id: int) -> Entitlement:
"""
Gets an entitlement by its id.
"""
entitlement = sdk.DiscordEntitlement()
result = Result(self._internal.get_entitlement(entitlement_id, entitlement))
if result != Result.ok:
raise get_exception(result)
return Entitlement(internal=Sku)
def get_entitlement_at(self, index: int) -> Entitlement:
"""
Gets an entitlement by index when iterating over a user's entitlements.
"""
entitlement = sdk.DiscordEntitlement()
result = Result(self._internal.get_entitlement_at(index, entitlement))
if result != Result.ok:
raise get_exception(result)
return Entitlement(internal=Sku)
def has_sku_entitlement(self, sku_id: int) -> bool:
"""
Returns whether or not the user is entitled to the given SKU ID.
"""
has_entitlement = ctypes.c_bool()
result = Result(self._internal.has_sku_entitlement(sku_id, has_entitlement))
if result != Result.ok:
raise get_exception(result)
return has_entitlement.value
def start_purchase(self, sku_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Opens the overlay to begin the in-app purchase dialogue for the given SKU ID.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.start_purchase.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.start_purchase(self._internal, sku_id, ctypes.c_void_p(), c_callback)
def on_entitlement_create(self, entitlement: Entitlement) -> None:
"""
Fires when the connected user receives a new entitlement, either through purchase or
through a developer grant.
"""
def on_entitlement_delete(self, entitlement: Entitlement) -> None:
"""
Fires when the connected user loses an entitlement, either by expiration, revocation, or
consumption in the case of consumable entitlements.
"""

View file

@ -0,0 +1,81 @@
import ctypes
import typing as t
from . import sdk
from .enum import PremiumType, Result, UserFlag
from .event import bind_events
from .exception import get_exception
from .model import User
class UserManager:
_internal: sdk.IDiscordUserManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordUserEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordUserEvents,
self._on_current_user_update
)
def _on_current_user_update(self, event_data):
self.on_current_user_update()
def get_current_user(self) -> User:
"""
Fetch information about the currently connected user account.
"""
user = sdk.DiscordUser()
result = Result(self._internal.get_current_user(self._internal, user))
if result != Result.ok:
raise get_exception(result)
return User(internal=user)
def get_user(self, user_id: int, callback: t.Callable[[Result], None]) -> None:
"""
Get user information for a given id.
Returns discordsdk.enum.Result (int) and User via callback.
"""
def c_callback(callback_data, result, user):
self._garbage.remove(c_callback)
result = Result(result)
if result == Result.ok:
callback(result, User(copy=user.contents))
else:
callback(result, None)
c_callback = self._internal.get_user.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.get_user(self._internal, user_id, ctypes.c_void_p(), c_callback)
def get_current_user_premium_type(self) -> PremiumType:
"""
Get the PremiumType for the currently connected user.
"""
premium_type = ctypes.c_int32()
result = Result(self._internal.get_current_user_premium_type(self._internal, premium_type))
if result != Result.ok:
raise get_exception(result)
return PremiumType(premium_type.value)
def current_user_has_flag(self, flag: UserFlag) -> bool:
"""
See whether or not the current user has a certain UserFlag on their account.
"""
has_flag = ctypes.c_bool()
result = Result(self._internal.current_user_has_flag(self._internal, flag, has_flag))
if result != Result.ok:
raise get_exception(result)
return has_flag.value
def on_current_user_update(self) -> None:
"""
Fires when the User struct of the currently connected user changes.
"""

View file

@ -0,0 +1,136 @@
import ctypes
import typing as t
from . import sdk
from .enum import Result
from .event import bind_events
from .exception import get_exception
from .model import InputMode
class VoiceManager:
_internal: sdk.IDiscordVoiceManager = None
_garbage: t.List[t.Any]
_events: sdk.IDiscordVoiceEvents
def __init__(self):
self._garbage = []
self._events = bind_events(
sdk.IDiscordVoiceEvents,
self._on_settings_update
)
def _on_settings_update(self, event_data):
self.on_settings_update()
def get_input_mode(self) -> InputMode:
"""
Get the current voice input mode for the user
"""
input_mode = sdk.DiscordInputMode()
result = Result(self._internal.get_input_mode(self._internal, input_mode))
if result != Result.ok:
raise get_exception(result)
return InputMode(internal=input_mode)
def set_input_mode(self, inputMode: InputMode, callback: t.Callable[[Result], None]) -> None:
"""
Sets a new voice input mode for the uesr.
Returns discordsdk.enum.Result (int) via callback.
"""
def c_callback(callback_data, result):
self._garbage.remove(c_callback)
result = Result(result)
callback(result)
c_callback = self._internal.set_input_mode.argtypes[-1](c_callback)
self._garbage.append(c_callback) # prevent it from being garbage collected
self._internal.set_input_mode(
self._internal,
inputMode._internal,
ctypes.c_void_p(),
c_callback
)
def is_self_mute(self) -> bool:
"""
Whether the connected user is currently muted.
"""
mute = ctypes.c_bool()
result = Result(self._internal.is_self_mute(self._internal, mute))
if result != Result.ok:
raise get_exception(result)
return mute.value
def set_self_mute(self, mute: bool) -> None:
"""
Mutes or unmutes the currently connected user.
"""
result = Result(self._internal.set_self_mute(self._internal, mute))
if result != Result.ok:
raise get_exception(result)
def is_self_deaf(self) -> bool:
"""
Whether the connected user is currently deafened.
"""
deaf = ctypes.c_bool()
result = Result(self._internal.is_self_deaf(self._internal, deaf))
if result != Result.ok:
raise get_exception(result)
return deaf.value
def set_self_deaf(self, deaf: bool) -> None:
"""
Deafens or undefeans the currently connected user.
"""
result = Result(self._internal.set_self_deaf(self._internal, deaf))
if result != Result.ok:
raise get_exception(result)
def is_local_mute(self, user_id: int) -> bool:
"""
Whether the given user is currently muted by the connected user.
"""
mute = ctypes.c_bool()
result = Result(self._internal.is_local_mute(self._internal, user_id, mute))
if result != Result.ok:
raise get_exception(result)
return mute.value
def set_local_mute(self, user_id: int, mute: bool) -> None:
"""
Mutes or unmutes the given user for the currently connected user.
"""
result = Result(self._internal.set_local_mute(self._internal, user_id, mute))
if result != Result.ok:
raise get_exception(result)
def get_local_volume(self, user_id: int) -> int:
"""
Gets the local volume for a given user.
"""
volume = ctypes.c_uint8()
result = Result(self._internal.get_local_volume(self._internal, user_id, volume))
if result != Result.ok:
raise get_exception(result)
return volume.value
def set_local_volume(self, user_id: int, volume: int) -> None:
"""
Sets the local volume for a given user.
"""
result = Result(self._internal.set_local_volume(self._internal, user_id, volume))
if result != Result.ok:
raise get_exception(result)
def on_settings_update(self) -> None:
# This event is not documented anywhere (yet?)
pass

23
py_discord_sdk/setup.py Normal file
View file

@ -0,0 +1,23 @@
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="discordsdk",
version="0.3dev",
author="LennyPhoenix & NathaanTFM",
author_email="lennyphoenixc@gmail.com",
description="Python wrapper around Discord's Game SDK library.",
license="LICENSE",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/LennyPhoenix/py-discord-sdk",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">= 3.5",
)

6
test.py Normal file
View file

@ -0,0 +1,6 @@
ShipType = 'sidewinder'
ShipName = ''
match ShipType:
case 'sidewinder':
ShipName = 'Sidewinder'
print(ShipName)