Init
This commit is contained in:
parent
f934b7ff99
commit
5c9564c703
44
L10n/de.strings
Normal file
44
L10n/de.strings
Normal 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
44
L10n/en.template
Normal 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
44
L10n/fr.strings
Normal 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
44
L10n/pt-BR.strings
Normal 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
44
L10n/ru.strings
Normal 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}, судно на орбите";
|
47
README.md
47
README.md
|
@ -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/).
|
||||
|
|
BIN
__pycache__/load.cpython-312.pyc
Normal file
BIN
__pycache__/load.cpython-312.pyc
Normal file
Binary file not shown.
BIN
lib/discord_game_sdk.dll
Normal file
BIN
lib/discord_game_sdk.dll
Normal file
Binary file not shown.
BIN
lib/discord_game_sdk.dylib
Normal file
BIN
lib/discord_game_sdk.dylib
Normal file
Binary file not shown.
BIN
lib/discord_game_sdk.so
Normal file
BIN
lib/discord_game_sdk.so
Normal file
Binary file not shown.
356
load.py
Normal file
356
load.py
Normal 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
157
py_discord_sdk/.gitignore
vendored
Normal 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
22
py_discord_sdk/LICENSE
Normal 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
134
py_discord_sdk/README.md
Normal 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()
|
||||
```
|
55
py_discord_sdk/discordsdk/__init__.py
Normal file
55
py_discord_sdk/discordsdk/__init__.py
Normal 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",
|
||||
]
|
110
py_discord_sdk/discordsdk/achievement.py
Normal file
110
py_discord_sdk/discordsdk/achievement.py
Normal 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
|
||||
"""
|
179
py_discord_sdk/discordsdk/activity.py
Normal file
179
py_discord_sdk/discordsdk/activity.py
Normal 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.
|
||||
"""
|
85
py_discord_sdk/discordsdk/application.py
Normal file
85
py_discord_sdk/discordsdk/application.py
Normal 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)
|
202
py_discord_sdk/discordsdk/discord.py
Normal file
202
py_discord_sdk/discordsdk/discord.py
Normal 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
|
166
py_discord_sdk/discordsdk/enum.py
Normal file
166
py_discord_sdk/discordsdk/enum.py
Normal 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
|
11
py_discord_sdk/discordsdk/event.py
Normal file
11
py_discord_sdk/discordsdk/event.py
Normal 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
|
19
py_discord_sdk/discordsdk/exception.py
Normal file
19
py_discord_sdk/discordsdk/exception.py
Normal 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))
|
71
py_discord_sdk/discordsdk/image.py
Normal file
71
py_discord_sdk/discordsdk/image.py
Normal 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)
|
801
py_discord_sdk/discordsdk/lobby.py
Normal file
801
py_discord_sdk/discordsdk/lobby.py
Normal 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.
|
||||
"""
|
329
py_discord_sdk/discordsdk/model.py
Normal file
329
py_discord_sdk/discordsdk/model.py
Normal 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
|
111
py_discord_sdk/discordsdk/network.py
Normal file
111
py_discord_sdk/discordsdk/network.py
Normal 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.
|
||||
"""
|
104
py_discord_sdk/discordsdk/overlay.py
Normal file
104
py_discord_sdk/discordsdk/overlay.py
Normal 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)
|
||||
"""
|
84
py_discord_sdk/discordsdk/relationship.py
Normal file
84
py_discord_sdk/discordsdk/relationship.py
Normal 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.
|
||||
"""
|
1538
py_discord_sdk/discordsdk/sdk.py
Normal file
1538
py_discord_sdk/discordsdk/sdk.py
Normal file
File diff suppressed because it is too large
Load diff
200
py_discord_sdk/discordsdk/storage.py
Normal file
200
py_discord_sdk/discordsdk/storage.py
Normal 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)
|
164
py_discord_sdk/discordsdk/store.py
Normal file
164
py_discord_sdk/discordsdk/store.py
Normal 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.
|
||||
"""
|
81
py_discord_sdk/discordsdk/user.py
Normal file
81
py_discord_sdk/discordsdk/user.py
Normal 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.
|
||||
"""
|
136
py_discord_sdk/discordsdk/voice.py
Normal file
136
py_discord_sdk/discordsdk/voice.py
Normal 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
23
py_discord_sdk/setup.py
Normal 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",
|
||||
)
|
Loading…
Reference in a new issue