├── .gitignore ├── LICENSE ├── README.md ├── discord ├── __init__.py ├── achievement.py ├── activity.py ├── application.py ├── enum.py ├── event.py ├── exception.py ├── image.py ├── lobby.py ├── model.py ├── network.py ├── overlay.py ├── relationship.py ├── sdk.py ├── storage.py ├── store.py ├── user.py └── voice.py └── examples ├── activity.py ├── image.py ├── network.py ├── relationship.py └── user.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | *.py 7 | *.dll 8 | *.txt 9 | !discord/*.py 10 | !examples/*.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 NathaanTFM 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NOTE: Most Discord GameSDK features are going deprecated. This repo will get updated if there's any major GameSDK updates 2 | 3 | # Discord Game SDK for Python 4 | 5 | This is **not** a module (for an installable module, check out [LennyPhoenix's fork](https://github.com/LennyPhoenix/py-discord-sdk)). 6 | This was made for **Python >= 3.5** and **Discord Game SDK 3.2.0** 7 | 8 | This is a **Work In Progress:** it might not work as expected or not work at all. This was made for testing purposes. 9 | 10 | ## Installation 11 | 12 | - Download: 13 | - [Discord Game SDK (3.2.0)](https://dl-game-sdk.discordapp.net/3.2.0/discord_game_sdk.zip) 14 | - [Discord Game SDK for Python](https://github.com/NathaanTFM/discord-game-sdk-python/archive/master.zip) 15 | 16 | - Grab the DLL from `discord_game_sdk.zip` in the `lib` directory and put it in your project directory 17 | - Grab the `discord` directory from `master.zip` and put it in your project directory 18 | 19 | ## Documentation 20 | 21 | 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. 22 | 23 | ## Features 24 | 25 | * Should be working: 26 | * **ActivityManager** 27 | * **ImageManager** 28 | * **NetworkManager** 29 | * **RelationshipManager** 30 | * **StorageManager** 31 | * **UserManager** 32 | 33 | * Should be working, but need more testing: 34 | * **AchievementManager** (not tested at all) 35 | * **ApplicationManager** (especially the functions `GetTicket` and `ValidateOrExit`) 36 | * **LobbyManager** 37 | * **OverlayManager** (especially the new functions) 38 | * **StoreManager** (not tested at all) 39 | * **VoiceManager** 40 | 41 | ## Contributing 42 | 43 | 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**! 44 | 45 | You can also **report issues**. Just open an issue and I will look into it! 46 | 47 | ## Examples 48 | 49 | You can find more examples in the `examples/` directory. 50 | 51 | Create a Discord instance 52 | 53 | ```python 54 | from discord import Discord 55 | from discord.enum import CreateFlags 56 | import time 57 | 58 | app = Discord(APPLICATION_ID, CreateFlags.Default) 59 | 60 | # Don't forget to call RunCallbacks 61 | while 1: 62 | time.sleep(1/10) 63 | app.RunCallbacks() 64 | ``` 65 | 66 | Get current user 67 | 68 | ```python 69 | from discord import Discord 70 | from discord.enum import CreateFlags 71 | import time 72 | 73 | app = Discord(APPLICATION_ID, CreateFlags.Default) 74 | 75 | userManager = app.GetUserManager() 76 | def onCurrUserUpdate(): 77 | user = userManager.GetCurrentUser() 78 | print(f"Current user : {user.Username}#{user.Discriminator}") 79 | 80 | userManager.OnCurrentUserUpdate = onCurrUserUpdate 81 | 82 | # Don't forget to call RunCallbacks 83 | while 1: 84 | time.sleep(1/10) 85 | app.RunCallbacks() 86 | ``` 87 | 88 | Set activity 89 | 90 | ```python 91 | from discord import Discord 92 | from discord.model import Activity 93 | from discord.enum import Result, CreateFlags 94 | import time 95 | 96 | app = Discord(APPLICATION_ID, CreateFlags.Default) 97 | 98 | activityManager = app.GetActivityManager() 99 | 100 | activity = Activity() 101 | activity.State = "Testing Game SDK" 102 | activity.Party.Id = "my_super_party_id" 103 | activity.Party.Size.CurrentSize = 4 104 | activity.Party.Size.MaxSize = 8 105 | activity.Secrets.Join = "my_super_secret" 106 | 107 | def callback(result): 108 | if result == Result.Ok: 109 | print("Successfully set the activity!") 110 | else: 111 | raise Exception(result) 112 | 113 | activityManager.UpdateActivity(activity, callback) 114 | 115 | # Don't forget to call RunCallbacks 116 | while 1: 117 | time.sleep(1/10) 118 | app.RunCallbacks() 119 | ``` 120 | -------------------------------------------------------------------------------- /discord/__init__.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .activity import ActivityManager 3 | from .relationship import RelationshipManager 4 | from .image import ImageManager 5 | from .user import UserManager 6 | from .lobby import LobbyManager 7 | from .network import NetworkManager 8 | from .overlay import OverlayManager 9 | from .application import ApplicationManager 10 | from .storage import StorageManager 11 | from .store import StoreManager 12 | from .voice import VoiceManager 13 | from .achievement import AchievementManager 14 | from .enum import Result, LogLevel, CreateFlags 15 | from .exception import getException 16 | from typing import Callable 17 | import ctypes 18 | 19 | class Discord: 20 | def __init__(self, clientId: int, flags: CreateFlags): 21 | self.core = None 22 | 23 | self._activityManager = ActivityManager() 24 | self._relationshipManager = RelationshipManager() 25 | self._imageManager = ImageManager() 26 | self._userManager = UserManager() 27 | self._lobbyManager = LobbyManager() 28 | self._networkManager = NetworkManager() 29 | self._overlayManager = OverlayManager() 30 | self._applicationManager = ApplicationManager() 31 | self._storageManager = StorageManager() 32 | self._storeManager = StoreManager() 33 | self._voiceManager = VoiceManager() 34 | self._achievementManager = AchievementManager() 35 | 36 | self._garbage = [] 37 | 38 | version = sdk.DiscordVersion(3) 39 | 40 | params = sdk.DiscordCreateParams() 41 | params.client_id = clientId 42 | params.flags = flags 43 | 44 | sdk.DiscordCreateParamsSetDefault(params) 45 | params.activity_events = self._activityManager._events 46 | params.relationship_events = self._relationshipManager._events 47 | params.image_events = self._imageManager._events 48 | params.user_events = self._userManager._events 49 | params.lobby_events = self._lobbyManager._events 50 | params.network_events = self._networkManager._events 51 | params.overlay_events = self._overlayManager._events 52 | params.application_events = self._applicationManager._events 53 | params.storage_events = self._storageManager._events 54 | params.store_events = self._storeManager._events 55 | params.voice_events = self._voiceManager._events 56 | params.achievement_events = self._achievementManager._events 57 | 58 | pointer = ctypes.POINTER(sdk.IDiscordCore)() 59 | 60 | result = Result(sdk.DiscordCreate(version, params, pointer)) 61 | if result != Result.Ok: 62 | raise getException(result) 63 | 64 | self.core = pointer.contents 65 | 66 | def __del__(self): 67 | if self.core: 68 | self.core.destroy(self.core) 69 | self.core = None 70 | 71 | def SetLogHook(self, min_level: LogLevel, hook: Callable[[LogLevel, str], None]) -> None: 72 | """ 73 | Registers a logging callback function with the minimum level of message to receive. 74 | """ 75 | def CHook(hook_data, level, message): 76 | level = LogLevel(level) 77 | hook(level, message.decode("utf8")) 78 | 79 | CHook = self.core.set_log_hook.argtypes[-1](CHook) 80 | self._garbage.append(CHook) 81 | 82 | self.core.set_log_hook(self.core, min_level.value, ctypes.c_void_p(), CHook) 83 | 84 | def RunCallbacks(self) -> None: 85 | """ 86 | Runs all pending SDK callbacks. 87 | """ 88 | result = Result(self.core.run_callbacks(self.core)) 89 | if result != Result.Ok: 90 | raise getException(result) 91 | 92 | def GetActivityManager(self) -> ActivityManager: 93 | """ 94 | Fetches an instance of the manager for interfacing with activies in the SDK. 95 | """ 96 | if not self._activityManager._internal: 97 | self._activityManager._internal = self.core.get_activity_manager(self.core).contents 98 | 99 | return self._activityManager 100 | 101 | def GetRelationshipManager(self) -> RelationshipManager: 102 | """ 103 | Fetches an instance of the manager for interfacing with relationships in the SDK. 104 | """ 105 | if not self._relationshipManager._internal: 106 | self._relationshipManager._internal = self.core.get_relationship_manager(self.core).contents 107 | 108 | return self._relationshipManager 109 | 110 | def GetImageManager(self) -> ImageManager: 111 | """ 112 | Fetches an instance of the manager for interfacing with images in the SDK. 113 | """ 114 | if not self._imageManager._internal: 115 | self._imageManager._internal = self.core.get_image_manager(self.core).contents 116 | 117 | return self._imageManager 118 | 119 | def GetUserManager(self) -> UserManager: 120 | """ 121 | Fetches an instance of the manager for interfacing with users in the SDK. 122 | """ 123 | if not self._userManager._internal: 124 | self._userManager._internal = self.core.get_user_manager(self.core).contents 125 | 126 | return self._userManager 127 | 128 | def GetLobbyManager(self) -> LobbyManager: 129 | """ 130 | Fetches an instance of the manager for interfacing with lobbies in the SDK. 131 | """ 132 | if not self._lobbyManager._internal: 133 | self._lobbyManager._internal = self.core.get_lobby_manager(self.core).contents 134 | 135 | return self._lobbyManager 136 | 137 | def GetNetworkManager(self) -> NetworkManager: 138 | """ 139 | Fetches an instance of the manager for interfacing with networking in the SDK. 140 | """ 141 | if not self._networkManager._internal: 142 | self._networkManager._internal = self.core.get_network_manager(self.core).contents 143 | 144 | return self._networkManager 145 | 146 | def GetOverlayManager(self) -> OverlayManager: 147 | """ 148 | Fetches an instance of the manager for interfacing with the overlay in the SDK. 149 | """ 150 | if not self._overlayManager._internal: 151 | self._overlayManager._internal = self.core.get_overlay_manager(self.core).contents 152 | 153 | return self._overlayManager 154 | 155 | def GetApplicationManager(self) -> ApplicationManager: 156 | """ 157 | Fetches an instance of the manager for interfacing with applications in the SDK. 158 | """ 159 | if not self._applicationManager._internal: 160 | self._applicationManager._internal = self.core.get_application_manager(self.core).contents 161 | 162 | return self._applicationManager 163 | 164 | def GetStorageManager(self) -> StorageManager: 165 | """ 166 | Fetches an instance of the manager for interfacing with storage in the SDK. 167 | """ 168 | if not self._storageManager._internal: 169 | self._storageManager._internal = self.core.get_storage_manager(self.core).contents 170 | 171 | return self._storageManager 172 | 173 | def GetStoreManager(self) -> StoreManager: 174 | """ 175 | Fetches an instance of the manager for interfacing with SKUs and Entitlements in the SDK. 176 | """ 177 | if not self._storeManager._internal: 178 | self._storeManager._internal = self.core.get_store_manager(self.core).contents 179 | 180 | return self._storeManager 181 | 182 | def GetVoiceManager(self) -> VoiceManager: 183 | """ 184 | Fetches an instance of the manager for interfacing with voice chat in the SDK. 185 | """ 186 | if not self._voiceManager._internal: 187 | self._voiceManager._internal = self.core.get_voice_manager(self.core).contents 188 | 189 | return self._voiceManager 190 | 191 | def GetAchievementManager(self) -> AchievementManager: 192 | """ 193 | Fetches an instance of the manager for interfacing with achievements in the SDK. 194 | """ 195 | if not self._achievementManager._internal: 196 | self._achievementManager._internal = self.core.get_achievement_manager(self.core).contents 197 | 198 | return self._achievementManager 199 | -------------------------------------------------------------------------------- /discord/achievement.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .enum import Result 3 | from .model import UserAchievement 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable 7 | import ctypes 8 | 9 | class AchievementManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordAchievementEvents, 14 | self._OnUserAchievementUpdate 15 | ) 16 | 17 | def _OnUserAchievementUpdate(self, event_data, user_achievement): 18 | self.OnUserAchievementUpdate(UserAchievement(copy = user_achievement.contents)) 19 | 20 | def SetUserAchievement(self, achievementId: int, percentComplete: int, callback: Callable[[Result], None]) -> None: 21 | """ 22 | Updates the current user's status for a given achievement. 23 | 24 | Returns discord.enum.Result via callback. 25 | """ 26 | def CCallback(callback_data, result): 27 | self._garbage.remove(CCallback) 28 | result = Result(result) 29 | callback(result) 30 | 31 | CCallback = self._internal.set_user_achievement.argtypes[-1](CCallback) 32 | self._garbage.append(CCallback) # prevent it from being garbage collected 33 | 34 | self._internal.set_user_achievement(self._internal, achievementId, percentComplete, ctypes.c_void_p(), CCallback) 35 | 36 | def FetchUserAchievements(self, callback: Callable[[Result], None]) -> None: 37 | """ 38 | Loads a stable list of the current user's achievements to iterate over. 39 | 40 | Returns discord.enum.Result via callback. 41 | """ 42 | def CCallback(callback_data, result): 43 | self._garbage.remove(CCallback) 44 | result = Result(result) 45 | callback(result) 46 | 47 | CCallback = self._internal.fetch_user_achievements.argtypes[-1](CCallback) 48 | self._garbage.append(CCallback) # prevent it from being garbage collected 49 | 50 | self._internal.fetch_user_achievements(self._internal, ctypes.c_void_p(), CCallback) 51 | 52 | def CountUserAchievements(self) -> int: 53 | """ 54 | Counts the list of a user's achievements for iteration. 55 | """ 56 | count = ctypes.c_int32() 57 | self._internal.count_user_achievements(self._internal, count) 58 | return count.value 59 | 60 | def GetUserAchievementAt(self, index: int) -> UserAchievement: 61 | """ 62 | Gets the user's achievement at a given index of their list of achievements. 63 | """ 64 | achievement = sdk.DiscordUserAchievement() 65 | result = Result(self._internal.get_user_achievement_at(self._internal, index, achievement)) 66 | if result != Result.Ok: 67 | raise getException(result) 68 | 69 | return UserAchievement(internal = achievement) 70 | 71 | def GetUserAchievement(self, achievementId: int) -> None: 72 | """ 73 | Gets the user achievement for the given achievement id. 74 | """ 75 | achievement = sdk.DiscordUserAchievement() 76 | result = Result(self._internal.get_user_achievement(self._internal, achievementId, achievement)) 77 | if result != Result.Ok: 78 | raise getException(result) 79 | 80 | return UserAchievement(internal = achievement) 81 | 82 | def OnUserAchievementUpdate(self, achievement: UserAchievement) -> None: 83 | """ 84 | Fires when an achievement is updated for the currently connected user 85 | """ 86 | pass -------------------------------------------------------------------------------- /discord/activity.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import User, Activity 3 | from .event import bindEvents 4 | from .enum import Result, ActivityJoinRequestReply, ActivityActionType 5 | from typing import Callable 6 | import ctypes 7 | 8 | class ActivityManager: 9 | def __init__(self): 10 | self._internal = None 11 | self._garbage = [] 12 | self._events = bindEvents(sdk.IDiscordActivityEvents, 13 | self._OnActivityJoin, 14 | self._OnActivitySpectate, 15 | self._OnActivityJoinRequest, 16 | self._OnActivityInvite 17 | ) 18 | 19 | def _OnActivityJoin(self, event_data, secret): 20 | self.OnActivityJoin(secret.decode("utf8")) 21 | 22 | def _OnActivitySpectate(self, event_data, secret): 23 | self.OnActivitySpectate(secret.decode("utf8")) 24 | 25 | def _OnActivityJoinRequest(self, event_data, user): 26 | self.OnActivityJoinRequest(User(copy = user.contents)) 27 | 28 | def _OnActivityInvite(self, event_data, type, user, activity): 29 | self.OnActivityInvite(type, User(copy = user.contents), Activity(copy = activity.contents)) 30 | 31 | def RegisterCommand(self, command: str) -> Result: 32 | """ 33 | Registers a command by which Discord can launch your game. 34 | """ 35 | result = Result(self._internal.register_command(self._internal, command.encode("utf8"))) 36 | return result 37 | 38 | def RegisterSteam(self, steamId: int) -> Result: 39 | """ 40 | Registers your game's Steam app id for the protocol `steam://run-game-id/`. 41 | """ 42 | result = Result(self._internal.register_steam(self._internal, steamId)) 43 | return result 44 | 45 | def UpdateActivity(self, activity: Activity, callback: Callable[[Result], None]) -> None: 46 | """ 47 | Set a user's presence in Discord to a new activity. 48 | 49 | Returns discord.enum.Result (int) via callback. 50 | """ 51 | def CCallback(callback_data, result): 52 | self._garbage.remove(CCallback) 53 | result = Result(result) 54 | callback(result) 55 | 56 | CCallback = self._internal.update_activity.argtypes[-1](CCallback) 57 | self._garbage.append(CCallback) # prevent it from being garbage collected 58 | 59 | self._internal.update_activity(self._internal, activity._internal, ctypes.c_void_p(), CCallback) 60 | 61 | def ClearActivity(self, callback: Callable[[Result], None]) -> None: 62 | """ 63 | Clears a user's presence in Discord to make it show nothing. 64 | 65 | Returns discord.enum.Result (int) via callback. 66 | """ 67 | def CCallback(callback_data, result): 68 | self._garbage.remove(CCallback) 69 | result = Result(result) 70 | callback(result) 71 | 72 | CCallback = self._internal.clear_activity.argtypes[-1](CCallback) 73 | self._garbage.append(CCallback) # prevent it from being garbage collected 74 | 75 | self._internal.clear_activity(self._internal, ctypes.c_void_p(), CCallback) 76 | 77 | def SendRequestReply(self, userId: int, reply: ActivityJoinRequestReply, callback: Callable[[Result], None]) -> None: 78 | """ 79 | Sends a reply to an Ask to Join request. 80 | 81 | Returns discord.enum.Result (int) via callback. 82 | """ 83 | def CCallback(callback_data, result): 84 | self._garbage.remove(CCallback) 85 | result = Result(result) 86 | callback(result) 87 | 88 | CCallback = self._internal.send_request_reply.argtypes[-1](CCallback) 89 | self._garbage.append(CCallback) # prevent it from being garbage collected 90 | 91 | self._internal.send_request_reply(self._internal, userId, reply, ctypes.c_void_p(), CCallback) 92 | 93 | def SendInvite(self, userId: int, type: ActivityActionType, content: str, callback: Callable[[Result], None]) -> None: 94 | """ 95 | Sends a game invite to a given user. 96 | 97 | Returns discord.enum.Result (int) via callback. 98 | """ 99 | def CCallback(callback_data, result): 100 | self._garbage.remove(CCallback) 101 | result = Result(result) 102 | callback(result) 103 | 104 | CCallback = self._internal.send_invite.argtypes[-1](CCallback) 105 | self._garbage.append(CCallback) # prevent it from being garbage collected 106 | 107 | self._internal.send_invite(self._internal, userId, type, content.encode("utf8"), ctypes.c_void_p(), CCallback) 108 | 109 | def AcceptInvite(self, userId: int, callback: Callable[[Result], None]) -> None: 110 | """ 111 | Accepts a game invitation from a given userId. 112 | 113 | Returns discord.enum.Result (int) via callback. 114 | """ 115 | def CCallback(callback_data, result): 116 | self._garbage.remove(CCallback) 117 | result = Result(result) 118 | callback(result) 119 | 120 | CCallback = self._internal.accept_invite.argtypes[-1](CCallback) 121 | self._garbage.append(CCallback) # prevent it from being garbage collected 122 | 123 | self._internal.accept_invite(self._internal, userId, ctypes.c_void_p(), CCallback) 124 | 125 | def OnActivityJoin(self, joinSecret: str) -> None: 126 | """ 127 | Fires when a user accepts a game chat invite or receives confirmation from Asking to Join. 128 | """ 129 | pass 130 | 131 | def OnActivitySpectate(self, spectateSecret: str) -> None: 132 | """ 133 | Fires when a user accepts a spectate chat invite or clicks the Spectate button on a user's profile. 134 | """ 135 | pass 136 | 137 | def OnActivityJoinRequest(self, user: User) -> None: 138 | """ 139 | Fires when a user asks to join the current user's game. 140 | """ 141 | pass 142 | 143 | def OnActivityInvite(self, type: ActivityActionType, user: User, activity: Activity) -> None: 144 | """ 145 | Fires when the user receives a join or spectate invite. 146 | """ 147 | pass 148 | -------------------------------------------------------------------------------- /discord/application.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import OAuth2Token 3 | from .enum import Result 4 | from typing import Callable, Optional 5 | import ctypes 6 | 7 | class SignedAppTicket: 8 | def __init__(self): 9 | self.application_id = None 10 | self.user = None 11 | self.entitlements = None 12 | self.timestamp = None 13 | 14 | class ApplicationManager: 15 | def __init__(self): 16 | self._internal = None 17 | self._garbage = [] 18 | self._events = None 19 | 20 | def GetCurrentLocale(self) -> str: 21 | """ 22 | Get the locale the current user has Discord set to. 23 | """ 24 | locale = sdk.DiscordLocale() 25 | self._internal.get_current_locale(self._internal, locale) 26 | return locale.value.decode("utf8") 27 | 28 | def GetCurrentBranch(self) -> str: 29 | """ 30 | Get the name of pushed branch on which the game is running. 31 | """ 32 | branch = sdk.DiscordBranch() 33 | self._internal.get_current_branch(self._internal, branch) 34 | return branch.value.decode("utf8") 35 | 36 | def GetOAuth2Token(self, callback: Callable[[Result, Optional[OAuth2Token]], None]) -> None: 37 | """ 38 | Retrieve an oauth2 bearer token for the current user. 39 | 40 | Returns discord.enum.Result (int) and OAuth2Token (str) via callback. 41 | """ 42 | def CCallback(callback_data, result, oauth2_token): 43 | self._garbage.remove(CCallback) 44 | if result == Result.Ok: 45 | callback(result, OAuth2Token(copy = oauth2_token.contents)) 46 | else: 47 | callback(result, None) 48 | 49 | CCallback = self._internal.get_oauth2_token.argtypes[-1](CCallback) 50 | self._garbage.append(CCallback) # prevent it from being garbage collected 51 | 52 | self._internal.get_oauth2_token(self._internal, ctypes.c_void_p(), CCallback) 53 | 54 | def ValidateOrExit(self, callback: Callable[[Result], None]) -> None: 55 | """ 56 | Checks if the current user has the entitlement to run this game. 57 | 58 | Returns discord.enum.Result (int) via callback. 59 | """ 60 | def CCallback(callback_data, result): 61 | self._garbage.remove(CCallback) 62 | callback(result) 63 | 64 | CCallback = self._internal.validate_or_exit.argtypes[-1](CCallback) 65 | self._garbage.append(CCallback) # prevent it from being garbage collected 66 | 67 | self._internal.validate_or_exit(self._internal, ctypes.c_void_p(), CCallback) 68 | 69 | def GetTicket(self, callback: Callable[[Result, Optional[str]], None]) -> None: 70 | """ 71 | Get the signed app ticket for the current user. 72 | 73 | Returns discord.Enum.Result (int) and str via callback. 74 | """ 75 | def CCallback(callback_data, result, data): 76 | self._garbage.remove(CCallback) 77 | if result == Result.Ok: 78 | callback(result, data.contents.value.decode("utf8")) 79 | else: 80 | callback(result, None) 81 | 82 | CCallback = self._internal.get_ticket.argtypes[-1](CCallback) 83 | self._garbage.append(CCallback) # prevent it from being garbage collected 84 | 85 | self._internal.get_ticket(self._internal, ctypes.c_void_p(), CCallback) -------------------------------------------------------------------------------- /discord/enum.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 6): 4 | from enum import IntEnum, IntFlag 5 | else: 6 | from enum import IntEnum, IntEnum as IntFlag 7 | 8 | class Result(IntEnum): 9 | Ok = 0 10 | ServiceUnavailable = 1 11 | InvalidVersion = 2 12 | LockFailed = 3 13 | InternalError = 4 14 | InvalidPayload = 5 15 | InvalidCommand = 6 16 | InvalidPermissions = 7 17 | NotFetched = 8 18 | NotFound = 9 19 | Conflict = 10 20 | InvalidSecret = 11 21 | InvalidJoinSecret = 12 22 | NoEligibleActivity = 13 23 | InvalidInvite = 14 24 | NotAuthenticated = 15 25 | InvalidAccessToken = 16 26 | ApplicationMismatch = 17 27 | InvalidDataUrl = 18 28 | InvalidBase64 = 19 29 | NotFiltered = 20 30 | LobbyFull = 21 31 | InvalidLobbySecret = 22 32 | InvalidFilename = 23 33 | InvalidFileSize = 24 34 | InvalidEntitlement = 25 35 | NotInstalled = 26 36 | NotRunning = 27 37 | InsufficientBuffer = 28 38 | PurchaseCanceled = 29 39 | InvalidGuild = 30 40 | InvalidEvent = 31 41 | InvalidChannel = 32 42 | InvalidOrigin = 33 43 | RateLimited = 34 44 | OAuth2Error = 35 45 | SelectChannelTimeout = 36 46 | GetGuildTimeout = 37 47 | SelectVoiceForceRequired = 38 48 | CaptureShortcutAlreadyListening = 39 49 | UnauthorizedForAchievement = 40 50 | InvalidGiftCode = 41 51 | PurchaseError = 42 52 | TransactionAborted = 43 53 | DrawingInitFailed = 44 54 | 55 | class LogLevel(IntEnum): 56 | Error = 0 57 | Warning = 1 58 | Info = 2 59 | Debug = 3 60 | 61 | class CreateFlags(IntFlag): 62 | Default = 0 63 | NoRequireDiscord = 1 64 | 65 | class UserFlag(IntFlag): 66 | Partner = 2 67 | HypeSquadEvents = 4 68 | HypeSquadHouse1 = 64 69 | HypeSquadHouse2 = 128 70 | HypeSquadHouse3 = 256 71 | 72 | class PremiumType(IntEnum): 73 | None_ = 0 74 | Tier1 = 1 75 | Tier2 = 2 76 | 77 | class ActivityType(IntEnum): 78 | Playing = 0 79 | Streaming = 1 80 | Listening = 2 81 | Custom = 4 82 | 83 | class ActivityJoinRequestReply(IntEnum): 84 | No = 0 85 | Yes = 1 86 | Ignore = 2 87 | 88 | class ActivityActionType(IntEnum): 89 | Join = 1 90 | Spectate = 2 91 | 92 | class RelationshipType(IntEnum): 93 | None_ = 0 94 | Friend = 1 95 | Blocked = 2 96 | PendingIncoming = 3 97 | PendingOutgoing = 4 98 | Implicit = 5 99 | 100 | class Status(IntEnum): 101 | Offline = 0 102 | Online = 1 103 | Idle = 2 104 | DoNotDisturb = 3 105 | 106 | class ImageType(IntEnum): 107 | User = 0 108 | 109 | class LobbyType(IntEnum): 110 | Private = 1 111 | Public = 2 112 | 113 | class LobbySearchComparison(IntEnum): 114 | LessThanOrEqual = -2 115 | LessThan = -1 116 | Equal = 0 117 | GreaterThan = 1 118 | GreaterThanOrEqual = 2 119 | NotEqual = 3 120 | 121 | class LobbySearchCast(IntEnum): 122 | String = 1 123 | Number = 2 124 | 125 | class LobbySearchDistance(IntEnum): 126 | Local = 0 127 | Default = 1 128 | Extended = 2 129 | Global = 3 130 | 131 | class InputModeType(IntEnum): 132 | VoiceActivity = 0 133 | PushToTalk = 1 134 | 135 | class SkuType(IntEnum): 136 | Application = 1 137 | DLC = 2 138 | Consumable = 3 139 | Bundle = 4 140 | 141 | class EntitlementType(IntEnum): 142 | Purchase = 1 143 | PremiumSubscription = 2 144 | DeveloperGift = 3 145 | TestModePurchase = 4 146 | FreePurchase = 5 147 | UserGift = 6 148 | PremiumPurchase = 7 149 | 150 | class KeyVariant(IntEnum): 151 | Normal = 0 152 | Right = 1 153 | Left = 2 154 | 155 | class MouseButton(IntEnum): 156 | Left = 0 157 | Middle = 1 158 | Right = 2 159 | 160 | class ActivityPartyPrivacy(IntEnum): 161 | Private = 0 162 | Public = 1 163 | 164 | class ActivitySupportedPlatformFlags(IntFlag): 165 | Desktop = 1 166 | Android = 2 167 | iOS = 4 -------------------------------------------------------------------------------- /discord/event.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | def bindEvents(structure, *methods): 4 | contents = structure() 5 | for index, (name, func) in enumerate(structure._fields_): 6 | setattr(contents, name, func(methods[index])) 7 | 8 | pointer = ctypes.pointer(contents) 9 | return pointer -------------------------------------------------------------------------------- /discord/exception.py: -------------------------------------------------------------------------------- 1 | from .enum import Result 2 | 3 | class DiscordException(Exception): 4 | pass 5 | 6 | exceptions = {} 7 | 8 | # we dynamically create the exceptions 9 | for res in Result: 10 | exception = type(res.name, (DiscordException,), {}) 11 | 12 | globals()[res.name] = exception 13 | exceptions[res] = exception 14 | 15 | def getException(result): 16 | return exceptions.get(result, DiscordException)("result " + str(result.value)) -------------------------------------------------------------------------------- /discord/image.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import ImageDimensions, ImageHandle 3 | from .enum import Result 4 | from .exception import getException 5 | from typing import Callable, Optional 6 | import ctypes 7 | 8 | class ImageManager: 9 | def __init__(self): 10 | self._internal = None 11 | self._garbage = [] 12 | self._events = None 13 | 14 | def Fetch(self, handle: ImageHandle, refresh: bool, callback: Callable[[Result, Optional[ImageHandle]], None]) -> None: 15 | """ 16 | Prepares an image to later retrieve data about it. 17 | 18 | Returns discord.enum.Result (int) and ImageHandle via callback. 19 | """ 20 | def CCallback(callback_data, result, handle): 21 | self._garbage.remove(CCallback) 22 | result = Result(result) 23 | if result == Result.Ok: 24 | callback(result, ImageHandle(internal = handle)) 25 | else: 26 | callback(result, None) 27 | 28 | CCallback = self._internal.fetch.argtypes[-1](CCallback) 29 | self._garbage.append(CCallback) # prevent it from being garbage collected 30 | 31 | self._internal.fetch(self._internal, handle._internal, refresh, ctypes.c_void_p(), CCallback) 32 | 33 | def GetDimensions(self, handle: ImageHandle) -> ImageDimensions: 34 | """ 35 | Gets the dimension for the given user's avatar's source image 36 | """ 37 | dimensions = sdk.DiscordImageDimensions() 38 | result = Result(self._internal.get_dimensions(self._internal, handle._internal, dimensions)) 39 | if result != Result.Ok: 40 | raise getException(result) 41 | 42 | return ImageDimensions(internal = dimensions) 43 | 44 | def GetData(self, handle: ImageHandle) -> bytes: 45 | """ 46 | Gets the image data for a given user's avatar. 47 | """ 48 | dimensions = self.GetDimensions(handle) 49 | buffer = (ctypes.c_uint8 * (dimensions.Width * dimensions.Height * 4))() 50 | 51 | result = Result(self._internal.get_data(self._internal, handle._internal, buffer, len(buffer))) 52 | if result != Result.Ok: 53 | raise getException(result) 54 | 55 | return bytes(buffer) 56 | -------------------------------------------------------------------------------- /discord/lobby.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import Lobby, User 3 | from .enum import Result, LobbyType, LobbySearchComparison, LobbySearchCast, LobbySearchDistance 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable, Optional 7 | import ctypes 8 | 9 | class LobbyTransaction: 10 | def __init__(self, internal): 11 | self._internal = internal 12 | 13 | def SetType(self, type: LobbyType) -> None: 14 | """ 15 | Marks a lobby as private or public. 16 | """ 17 | result = Result(self._internal.set_type(self._internal, type)) 18 | if result != Result.Ok: 19 | raise getException(result) 20 | 21 | def SetOwner(self, userId: int) -> None: 22 | """ 23 | Sets a new owner for the lobby. 24 | """ 25 | result = Result(self._internal.set_owner(self._internal, userId)) 26 | if result != Result.Ok: 27 | raise getException(result) 28 | 29 | def SetCapacity(self, capacity: int) -> None: 30 | """ 31 | Sets a new capacity for the lobby. 32 | """ 33 | result = Result(self._internal.set_capacity(self._internal, capacity)) 34 | if result != Result.Ok: 35 | raise getException(result) 36 | 37 | def SetMetadata(self, key: str, value: str) -> None: 38 | """ 39 | Sets metadata value under a given key name for the lobby. 40 | """ 41 | metadataKey = sdk.DiscordMetadataKey() 42 | metadataKey.value = key.encode("utf8") 43 | 44 | metadataValue = sdk.DiscordMetadataValue() 45 | metadataValue.value = value.encode("utf8") 46 | 47 | result = Result(self._internal.set_metadata(self._internal, metadataKey, metadataValue)) 48 | if result != Result.Ok: 49 | raise getException(result) 50 | 51 | def DeleteMetadata(self, key: str) -> None: 52 | """ 53 | Deletes the lobby metadata for a key. 54 | """ 55 | metadataKey = sdk.DiscordMetadataKey() 56 | metadataKey.value = key.encode("utf8") 57 | 58 | result = Result(self._internal.delete_metadata(self._internal, metadataKey)) 59 | if result != Result.Ok: 60 | raise getException(result) 61 | 62 | def SetLocked(self, locked: bool) -> None: 63 | """ 64 | Sets the lobby to locked or unlocked. 65 | """ 66 | result = Result(self._internal.set_locked(self._internal, locked)) 67 | if result != Result.Ok: 68 | raise getException(result) 69 | 70 | class LobbyMemberTransaction: 71 | def __init__(self, internal): 72 | self._internal = internal 73 | 74 | def SetMetadata(self, key: str, value: str) -> None: 75 | """ 76 | Sets metadata value under a given key name for the current user. 77 | """ 78 | metadataKey = sdk.DiscordMetadataKey() 79 | metadataKey.value = key.encode("utf8") 80 | 81 | metadataValue = sdk.DiscordMetadataValue() 82 | metadataValue.value = value.encode("utf8") 83 | 84 | result = Result(self._internal.set_metadata(self._internal, metadataKey, metadataValue)) 85 | if result != Result.Ok: 86 | raise getException(result) 87 | 88 | def DeleteMetadata(self, key: str) -> None: 89 | """ 90 | Sets metadata value under a given key name for the current user. 91 | """ 92 | metadataKey = sdk.DiscordMetadataKey() 93 | metadataKey.value = key.encode("utf8") 94 | 95 | result = Result(self._internal.delete_metadata(self._internal, metadataKey)) 96 | if result != Result.Ok: 97 | raise getException(result) 98 | 99 | class LobbySearchQuery: 100 | def __init__(self, internal): 101 | self._internal = internal 102 | 103 | def Filter(self, key: str, comp: LobbySearchComparison, cast: LobbySearchCast, value: str) -> None: 104 | """ 105 | Filters lobbies based on metadata comparison. 106 | """ 107 | metadataKey = sdk.DiscordMetadataKey() 108 | metadataKey.value = key.encode("utf8") 109 | 110 | metadataValue = sdk.DiscordMetadataValue() 111 | metadataValue.value = value.encode("utf8") 112 | 113 | result = Result(self._internal.filter(self._internal, metadataKey, comp, cast, metadataValue)) 114 | if result != Result.Ok: 115 | raise getException(result) 116 | 117 | def Sort(self, key: str, cast: LobbySearchCast, value: str) -> None: 118 | """ 119 | Sorts the filtered lobbies based on "near-ness" to a given value. 120 | """ 121 | metadataKey = sdk.DiscordMetadataKey() 122 | metadataKey.value = key.encode("utf8") 123 | 124 | metadataValue = sdk.DiscordMetadataValue() 125 | metadataValue.value = value.encode("utf8") 126 | 127 | result = Result(self._internal.sort(self._internal, metadataKey, cast, metadataValue)) 128 | if result != Result.Ok: 129 | raise getException(result) 130 | 131 | def Limit(self, limit: int) -> None: 132 | """ 133 | Limits the number of lobbies returned in a search. 134 | """ 135 | result = Result(self._internal.limit(self._internal, limit)) 136 | if result != Result.Ok: 137 | raise getException(result) 138 | 139 | def Distance(self, distance: LobbySearchDistance) -> None: 140 | """ 141 | Filters lobby results to within certain regions relative to the user's location. 142 | """ 143 | result = Result(self._internal.distance(self._internal, distance)) 144 | if result != Result.Ok: 145 | raise getException(result) 146 | 147 | class LobbyManager: 148 | def __init__(self): 149 | self._internal = None 150 | self._garbage = [] 151 | self._events = bindEvents(sdk.IDiscordLobbyEvents, 152 | self._OnLobbyUpdate, 153 | self._OnLobbyDelete, 154 | self._OnMemberConnect, 155 | self._OnMemberUpdate, 156 | self._OnMemberDisconnect, 157 | self._OnLobbyMessage, 158 | self._OnSpeaking, 159 | self._OnNetworkMessage 160 | ) 161 | 162 | def _OnLobbyUpdate(self, event_data, lobby_id): 163 | self.OnLobbyUpdate(lobby_id) 164 | 165 | def _OnLobbyDelete(self, event_data, lobby_id, reason): 166 | self.OnLobbyDelete(lobby_id, reason) 167 | 168 | def _OnMemberConnect(self, event_data, lobby_id, user_id): 169 | self.OnMemberConnect(lobby_id, user_id) 170 | 171 | def _OnMemberUpdate(self, event_data, lobby_id, user_id): 172 | self.OnMemberUpdate(lobby_id, user_id) 173 | 174 | def _OnMemberDisconnect(self, event_data, lobby_id, user_id): 175 | self.OnMemberDisconnect(lobby_id, user_id) 176 | 177 | def _OnLobbyMessage(self, event_data, lobby_id, user_id, data, data_length): 178 | message = bytes(data[:data_length]).decode("utf8") 179 | self.OnLobbyMessage(lobby_id, user_id, message) 180 | 181 | def _OnSpeaking(self, event_data, lobby_id, user_id, speaking): 182 | self.OnSpeaking(lobby_id, user_id, speaking) 183 | 184 | def _OnNetworkMessage(self, event_data, lobby_id, user_id, channel_id, data, data_length): 185 | data = bytes(data[:data_length]) 186 | self.OnNetworkMessage(lobby_id, user_id, channel_id, data) 187 | 188 | def GetLobbyCreateTransaction(self) -> LobbyTransaction: 189 | """ 190 | Gets a Lobby transaction used for creating a new lobby 191 | """ 192 | transaction = ctypes.POINTER(sdk.IDiscordLobbyTransaction)() 193 | result = Result(self._internal.get_lobby_create_transaction(self._internal, transaction)) 194 | if result != Result.Ok: 195 | raise getException(result) 196 | 197 | return LobbyTransaction(internal = transaction.contents) 198 | 199 | def GetLobbyUpdateTransaction(self, lobbyId: int) -> LobbyTransaction: 200 | """ 201 | Gets a lobby transaction used for updating an existing lobby. 202 | """ 203 | transaction = ctypes.POINTER(sdk.IDiscordLobbyTransaction)() 204 | result = Result(self._internal.get_lobby_update_transaction(self._internal, lobbyId, transaction)) 205 | if result != Result.Ok: 206 | raise getException(result) 207 | 208 | return LobbyTransaction(internal = transaction.contents) 209 | 210 | def GetMemberUpdateTransaction(self, lobbyId: int, userId: int) -> LobbyMemberTransaction: 211 | """ 212 | Gets a new member transaction for a lobby member in a given lobby. 213 | """ 214 | transaction = ctypes.POINTER(sdk.IDiscordLobbyMemberTransaction)() 215 | result = Result(self._internal.get_member_update_transaction(self._internal, lobbyId, userId, transaction)) 216 | if result != Result.Ok: 217 | raise getException(result) 218 | 219 | return LobbyMemberTransaction(internal = transaction.contents) 220 | 221 | def CreateLobby(self, transaction: LobbyTransaction, callback: Callable[[Result, Optional[Lobby]], None]) -> None: 222 | """ 223 | Creates a lobby. 224 | 225 | Returns discord.enum.Result (int) and Lobby via callback. 226 | """ 227 | def CCallback(callback_data, result, lobby): 228 | self._garbage.remove(CCallback) 229 | result = Result(result) 230 | if result == Result.Ok: 231 | callback(result, Lobby(copy = lobby.contents)) 232 | else: 233 | callback(result, None) 234 | 235 | CCallback = self._internal.create_lobby.argtypes[-1](CCallback) 236 | self._garbage.append(CCallback) # prevent it from being garbage collected 237 | 238 | self._internal.create_lobby(self._internal, transaction._internal, ctypes.c_void_p(), CCallback) 239 | 240 | def UpdateLobby(self, lobbyId: int, transaction: LobbyTransaction, callback: Callable[[Result], None]) -> None: 241 | """ 242 | Updates a lobby with data from the given transaction. 243 | """ 244 | def CCallback(callback_data, result): 245 | self._garbage.remove(CCallback) 246 | result = Result(result) 247 | callback(result) 248 | 249 | CCallback = self._internal.update_lobby.argtypes[-1](CCallback) 250 | self._garbage.append(CCallback) # prevent it from being garbage collected 251 | 252 | self._internal.update_lobby(self._internal, lobbyId, transaction._internal, ctypes.c_void_p(), CCallback) 253 | 254 | def DeleteLobby(self, lobbyId: int, callback: Callable[[Result], None]) -> None: 255 | """ 256 | Deletes a given lobby. 257 | """ 258 | def CCallback(callback_data, result): 259 | self._garbage.remove(CCallback) 260 | result = Result(result) 261 | callback(result) 262 | 263 | CCallback = self._internal.delete_lobby.argtypes[-1](CCallback) 264 | self._garbage.append(CCallback) # prevent it from being garbage collected 265 | 266 | self._internal.delete_lobby(self._internal, lobbyId, ctypes.c_void_p(), CCallback) 267 | 268 | def ConnectLobby(self, lobbyId: int, lobbySecret: str, callback: Callable[[Result], None]) -> None: 269 | """ 270 | Connects the current user to a given lobby. 271 | """ 272 | def CCallback(callback_data, result, lobby): 273 | self._garbage.remove(CCallback) 274 | result = Result(result) 275 | if result == Result.Ok: 276 | callback(result, Lobby(copy = lobby.contents)) 277 | else: 278 | callback(result, None) 279 | 280 | CCallback = self._internal.connect_lobby.argtypes[-1](CCallback) 281 | self._garbage.append(CCallback) # prevent it from being garbage collected 282 | 283 | _lobbySecret = sdk.DiscordLobbySecret() 284 | _lobbySecret.value = lobbySecret.encode("utf8") 285 | 286 | self._internal.connect_lobby(self._internal, lobbyId, _lobbySecret, ctypes.c_void_p(), CCallback) 287 | 288 | def ConnectLobbyWithActivitySecret(self, activitySecret: str, callback: Callable[[Result, Optional[Lobby]], None]) -> None: 289 | """ 290 | Connects the current user to a lobby; requires the special activity secret from the lobby which is a concatenated lobbyId and secret. 291 | """ 292 | def CCallback(callback_data, result, lobby): 293 | self._garbage.remove(CCallback) 294 | result = Result(result) 295 | if result == Result.Ok: 296 | callback(result, Lobby(copy = lobby.contents)) 297 | else: 298 | callback(result, None) 299 | 300 | CCallback = self._internal.connect_lobby_with_activity_secret.argtypes[-1](CCallback) 301 | self._garbage.append(CCallback) # prevent it from being garbage collected 302 | 303 | _activitySecret = sdk.DiscordLobbySecret() 304 | _activitySecret.value = activitySecret.encode("utf8") 305 | 306 | self._internal.connect_lobby_with_activity_secret(self._internal, _activitySecret, ctypes.c_void_p(), CCallback) 307 | 308 | def GetLobbyActivitySecret(self, lobbyId: int) -> str: 309 | """ 310 | Gets the special activity secret for a given lobby. 311 | """ 312 | lobbySecret = sdk.DiscordLobbySecret() 313 | 314 | result = self._internal.get_lobby_activity_secret(self._internal, lobbyId, lobbySecret) 315 | if result != Result.Ok: 316 | raise getException(result) 317 | 318 | return lobbySecret.value.decode("utf8") 319 | 320 | def DisconnectLobby(self, lobbyId: int, callback: Callable[[Result], None]) -> None: 321 | """ 322 | Disconnects the current user from a lobby. 323 | 324 | Returns discord.enum.Result (int) via callback. 325 | """ 326 | def CCallback(callback_data, result): 327 | self._garbage.remove(CCallback) 328 | result = Result(result) 329 | callback(result) 330 | 331 | CCallback = self._internal.disconnect_lobby.argtypes[-1](CCallback) 332 | self._garbage.append(CCallback) # prevent it from being garbage collected 333 | 334 | self._internal.disconnect_lobby(self._internal, lobbyId, ctypes.c_void_p(), CCallback) 335 | 336 | def GetLobby(self, lobbyId: int) -> Lobby: 337 | """ 338 | Gets the lobby object for a given lobby id. 339 | """ 340 | lobby = sdk.DiscordLobby() 341 | 342 | result = Result(self._internal.get_lobby(self._internal, lobbyId, lobby)) 343 | if result != Result.Ok: 344 | raise getException(result) 345 | 346 | return Lobby(internal = lobby) 347 | 348 | def LobbyMetadataCount(self, lobbyId: int) -> int: 349 | """ 350 | Returns the number of metadata key/value pairs on a given lobby. 351 | """ 352 | count = ctypes.c_int32() 353 | 354 | result = Result(self._internal.lobby_metadata_count(self._internal, lobbyId, count)) 355 | if result != Result.Ok: 356 | raise getException(result) 357 | 358 | return count.value 359 | 360 | def GetLobbyMetadataKey(self, lobbyId: int, index: int) -> str: 361 | """ 362 | Returns the key for the lobby metadata at the given index. 363 | """ 364 | metadataKey = sdk.DiscordMetadataKey() 365 | 366 | result = Result(self._internal.get_lobby_metadata_key(self._internal, lobbyId, index, metadataKey)) 367 | if result != Result.Ok: 368 | raise getException(result) 369 | 370 | return metadataKey.value.decode("utf8") 371 | 372 | def GetLobbyMetadataValue(self, lobbyId: int, key: str) -> str: 373 | """ 374 | Returns lobby metadata value for a given key and id. 375 | """ 376 | metadataKey = sdk.DiscordMetadataKey() 377 | metadataKey.value = key.encode("utf8") 378 | 379 | metadataValue = sdk.DiscordMetadataValue() 380 | 381 | result = Result(self._internal.get_lobby_metadata_value(self._internal, lobbyId, metadataKey, metadataValue)) 382 | if result != Result.Ok: 383 | raise getException(result) 384 | 385 | return metadataValue.value.decode("utf8") 386 | 387 | def MemberCount(self, lobbyId: int) -> int: 388 | """ 389 | Get the number of members in a lobby. 390 | """ 391 | count = ctypes.c_int32() 392 | 393 | result = Result(self._internal.member_count(self._internal, lobbyId, count)) 394 | if result != Result.Ok: 395 | raise getException(result) 396 | 397 | return count.value 398 | 399 | def GetMemberUserId(self, lobbyId: int, index: int) -> int: 400 | """ 401 | Gets the user id of the lobby member at the given index. 402 | """ 403 | userId = sdk.DiscordUserId() 404 | 405 | result = Result(self._internal.get_member_user_id(self._internal, lobbyId, index, userId)) 406 | if result != Result.Ok: 407 | raise getException(result) 408 | 409 | return userId.value 410 | 411 | def GetMemberUser(self, lobbyId: int, userId: int) -> User: 412 | """ 413 | Gets the user object for a given user id. 414 | """ 415 | user = sdk.DiscordUser() 416 | 417 | result = Result(self._internal.get_member_user(self._internal, lobbyId, userId, user)) 418 | if result != Result.Ok: 419 | raise getException(result) 420 | 421 | return User(internal = user) 422 | 423 | def MemberMetadataCount(self, lobbyId: int, userId: int) -> int: 424 | """ 425 | Gets the number of metadata key/value pairs for the given lobby member. 426 | """ 427 | count = ctypes.c_int32() 428 | 429 | result = Result(self._internal.member_metadata_count(self._internal, lobbyId, userId, count)) 430 | if result != Result.Ok: 431 | raise getException(result) 432 | 433 | return count.value 434 | 435 | def GetMemberMetadataKey(self, lobbyId: int, userId: int, index: int) -> str: 436 | """ 437 | Gets the key for the lobby metadata at the given index on a lobby member. 438 | """ 439 | metadataKey = sdk.DiscordMetadataKey() 440 | 441 | result = Result(self._internal.get_member_metadata_key(self._internal, lobbyId, userId, index, metadataKey)) 442 | if result != Result.Ok: 443 | raise getException(result) 444 | 445 | return metadataKey.value.decode("utf8") 446 | 447 | def GetMemberMetadataValue(self, lobbyId: int, userId: int, key: str) -> str: 448 | """ 449 | Returns user metadata for a given key. 450 | """ 451 | metadataKey = sdk.DiscordMetadataKey() 452 | metadataKey.value = key.encode("utf8") 453 | 454 | metadataValue = sdk.DiscordMetadataValue() 455 | 456 | result = Result(self._internal.get_member_metadata_value(self._internal, lobbyId, userId, metadataKey, metadataValue)) 457 | if result != Result.Ok: 458 | raise getException(result) 459 | 460 | return metadataValue.value.decode("utf8") 461 | 462 | def UpdateMember(self, lobbyId: int, userId: int, transaction: LobbyMemberTransaction, callback: Callable[[Result], None]) -> None: 463 | """ 464 | Updates lobby member info for a given member of the lobby. 465 | 466 | Returns discord.enum.Result (int) via callback. 467 | """ 468 | def CCallback(callback_data, result): 469 | self._garbage.remove(CCallback) 470 | result = Result(result) 471 | callback(result) 472 | 473 | CCallback = self._internal.update_member.argtypes[-1](CCallback) 474 | self._garbage.append(CCallback) # prevent it from being garbage collected 475 | 476 | self._internal.update_member(self._internal, lobbyId, userId, transaction._internal, ctypes.c_void_p(), CCallback) 477 | 478 | def SendLobbyMessage(self, lobbyId: int, data: str, callback: Callable[[Result], None]) -> None: 479 | """ 480 | Sends a message to the lobby on behalf of the current user. 481 | 482 | Returns Discord.result (int) via callback. 483 | """ 484 | def CCallback(callback_data, result): 485 | self._garbage.remove(CCallback) 486 | result = Result(result) 487 | callback(result) 488 | 489 | CCallback = self._internal.send_lobby_message.argtypes[-1](CCallback) 490 | self._garbage.append(CCallback) # prevent it from being garbage collected 491 | 492 | data = data.encode("utf8") 493 | data = (ctypes.c_uint8 * len(data))(*data) 494 | self._internal.send_lobby_message(self._internal, lobbyId, data, len(data), ctypes.c_void_p(), CCallback) 495 | 496 | def GetSearchQuery(self) -> LobbySearchQuery: 497 | """ 498 | Creates a search object to search available lobbies. 499 | """ 500 | search_query = (ctypes.POINTER(sdk.IDiscordLobbySearchQuery))() 501 | result = Result(self._internal.get_search_query(self._internal, ctypes.byref(search_query))) 502 | if result != Result.Ok: 503 | raise getException(result) 504 | 505 | return LobbySearchQuery(internal = search_query.contents) 506 | 507 | def Search(self, search: LobbySearchQuery, callback: Callable[[Result], None]) -> None: 508 | """ 509 | Searches available lobbies based on the search criteria chosen in the LobbySearchQuery member functions. 510 | 511 | Lobbies that meet the criteria are then globally filtered, and can be accessed via iteration with LobbyCount() and GetLobbyId(). The callback fires when the list of lobbies is stable and ready for iteration. 512 | 513 | Returns discord.enum.Result (int) via callback. 514 | """ 515 | def CCallback(callback_data, result): 516 | self._garbage.remove(CCallback) 517 | result = Result(result) 518 | callback(result) 519 | 520 | CCallback = self._internal.search.argtypes[-1](CCallback) 521 | self._garbage.append(CCallback) # prevent it from being garbage collected 522 | 523 | self._internal.search(self._internal, search._internal, ctypes.c_void_p(), CCallback) 524 | 525 | def LobbyCount(self) -> int: 526 | """ 527 | Get the number of lobbies that match the search. 528 | """ 529 | count = ctypes.c_int32() 530 | self._internal.lobby_count(self._internal, count) 531 | return count.value 532 | 533 | def GetLobbyId(self, index: int) -> int: 534 | """ 535 | Returns the id for the lobby at the given index. 536 | """ 537 | 538 | lobbyId = sdk.DiscordLobbyId() 539 | 540 | result = Result(self._internal.get_lobby_id(self._internal, index, lobbyId)) 541 | if result != Result.Ok: 542 | raise getException(result) 543 | 544 | return lobbyId.value 545 | 546 | def ConnectVoice(self, lobbyId: int, callback: Callable[[Result], None]) -> None: 547 | """ 548 | Connects to the voice channel of the current lobby. 549 | 550 | Returns discord.enum.Result (int) via callback. 551 | """ 552 | def CCallback(callback_data, result): 553 | self._garbage.remove(CCallback) 554 | result = Result(result) 555 | callback(result) 556 | 557 | CCallback = self._internal.connect_voice.argtypes[-1](CCallback) 558 | self._garbage.append(CCallback) # prevent it from being garbage collected 559 | 560 | self._internal.connect_voice(self._internal, lobbyId, ctypes.c_void_p(), CCallback) 561 | 562 | def DisconnectVoice(self, lobbyId: int, callback: Callable[[Result], None]) -> None: 563 | """ 564 | Disconnects from the voice channel of a given lobby. 565 | 566 | Returns discord.enum.Result (int) via callback. 567 | """ 568 | def CCallback(callback_data, result): 569 | self._garbage.remove(CCallback) 570 | result = Result(result) 571 | callback(result) 572 | 573 | CCallback = self._internal.disconnect_voice.argtypes[-1](CCallback) 574 | self._garbage.append(CCallback) # prevent it from being garbage collected 575 | 576 | self._internal.disconnect_voice(self._internal, lobbyId, ctypes.c_void_p(), CCallback) 577 | 578 | def OnLobbyUpdate(self, lobbyId: int) -> None: 579 | """ 580 | Fires when a lobby is updated. 581 | """ 582 | pass 583 | 584 | def OnLobbyDelete(self, lobbyId: int, reason: str) -> None: 585 | """ 586 | Fired when a lobby is deleted. 587 | """ 588 | pass 589 | 590 | def OnMemberConnect(self, lobbyId: int, userId: int) -> None: 591 | """ 592 | Fires when a new member joins the lobby. 593 | """ 594 | pass 595 | 596 | def OnMemberUpdate(self, lobbyId: int, userId: int) -> None: 597 | """ 598 | Fires when data for a lobby member is updated. 599 | """ 600 | pass 601 | 602 | def OnMemberDisconnect(self, lobbyId: int, userId: int) -> None: 603 | """ 604 | Fires when a member leaves the lobby. 605 | """ 606 | pass 607 | 608 | def OnLobbyMessage(self, lobbyId: int, userId: int, message: str) -> None: 609 | """ 610 | Fires when a message is sent to the lobby. 611 | """ 612 | pass 613 | 614 | def OnSpeaking(self, lobbyId: int, userId: int, speaking: bool) -> None: 615 | """ 616 | Fires when a user connected to voice starts or stops speaking. 617 | """ 618 | pass 619 | 620 | def ConnectNetwork(self, lobbyId: int) -> None: 621 | """ 622 | Connects to the networking layer for the given lobby ID. 623 | """ 624 | result = Result(self._internal.connect_network(self._internal, lobbyId)) 625 | if result != Result.Ok: 626 | raise getException(result) 627 | 628 | def DisconnectNetwork(self, lobbyId: int) -> None: 629 | """ 630 | Disconnects from the networking layer for the given lobby ID. 631 | """ 632 | result = Result(self._internal.disconnect_network(self._internal, lobbyId)) 633 | if result != Result.Ok: 634 | raise getException(result) 635 | 636 | def FlushNetwork(self) -> None: 637 | """ 638 | Flushes the network. Call this when you're done sending messages. 639 | """ 640 | result = Result(self._internal.flush_network(self._internal)) 641 | if result != Result.Ok: 642 | raise getException(result) 643 | 644 | def OpenNetworkChannel(self, lobbyId: int, channelId: int, reliable: bool) -> None: 645 | """ 646 | Opens a network channel to all users in a lobby on the given channel number. No need to iterate over everyone! 647 | """ 648 | result = Result(self._internal.open_network_channel(self._internal, lobbyId, channelId, reliable)) 649 | if result != Result.Ok: 650 | raise getException(result) 651 | 652 | def SendNetworkMessage(self, lobbyId: int, userId: int, channelId: int, data: bytes) -> None: 653 | """ 654 | Sends a network message to the given user ID that is a member of the given lobby ID over the given channel ID. 655 | """ 656 | data = (ctypes.c_uint8 * len(data))(*data) 657 | result = Result(self._internal.send_network_message(self._internal, lobbyId, userId, channelId, data, len(data))) 658 | if result != Result.Ok: 659 | raise getException(result) 660 | 661 | def OnNetworkMessage(self, lobbyId: int, userId: int, channelId: int, data: bytes) -> None: 662 | """ 663 | Fires when the user receives a message from the lobby's networking layer. 664 | """ 665 | pass 666 | -------------------------------------------------------------------------------- /discord/model.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .enum import Status, RelationshipType, ImageType, LobbyType, InputModeType, SkuType, EntitlementType, ActivityPartyPrivacy, ActivitySupportedPlatformFlags 3 | from enum import Enum 4 | import ctypes 5 | 6 | class Model: 7 | def __init__(self, **kwargs): 8 | self._internal = kwargs.get("internal", self._struct_()) 9 | if "copy" in kwargs: 10 | ctypes.memmove(ctypes.byref(self._internal), ctypes.byref(kwargs["copy"]), ctypes.sizeof(self._struct_)) 11 | 12 | self._fields = {} 13 | 14 | for name, field, ftype in self._fields_: 15 | self._fields[name] = (field, ftype) 16 | if issubclass(ftype, Model): 17 | setattr(self, "_" + field, ftype(internal = getattr(self._internal, field))) 18 | 19 | def __getattribute__(self, key): 20 | if key.startswith("_"): 21 | return super().__getattribute__(key) 22 | else: 23 | field = self._fields[key] 24 | value = getattr(self._internal, field[0]) 25 | if field[1] == int: 26 | return int(value) 27 | elif field[1] == str: 28 | return value.decode("utf8") 29 | elif field[1] == bool: 30 | return bool(value) 31 | elif issubclass(field[1], Model): 32 | return getattr(self, "_" + field[0]) 33 | elif issubclass(field[1], Enum): 34 | return field[1](int(value)) 35 | else: 36 | raise TypeError(field[1]) 37 | 38 | def __setattr__(self, key, value): 39 | if key.startswith("_"): 40 | super().__setattr__(key, value) 41 | else: 42 | field = self._fields[key] 43 | if field[1] == int: 44 | value = int(value) 45 | setattr(self._internal, field[0], value) 46 | elif field[1] == str: 47 | value = value.encode("utf8") 48 | setattr(self._internal, field[0], value) 49 | elif field[1] == bool: 50 | value = bool(value) 51 | setattr(self._internal, field[0], value) 52 | elif issubclass(field[1], Model): 53 | setattr(self, "_" + field[0], value) 54 | setattr(self._internal, field[0], value._internal) 55 | elif issubclass(field[1], Enum): 56 | setattr(self._internal, field[0], value.value) 57 | else: 58 | raise TypeError(field[1]) 59 | 60 | def __dir__(self): 61 | return super().__dir__() + list(self._fields.keys()) 62 | 63 | class User(Model): 64 | _struct_ = sdk.DiscordUser 65 | _fields_ = [ 66 | ("Id", "id", int), 67 | ("Username", "username", str), 68 | ("Discriminator", "discriminator", str), 69 | ("Avatar", "avatar", str), 70 | ("Bot", "bot", bool) 71 | ] 72 | 73 | class ActivityTimestamps(Model): 74 | _struct_ = sdk.DiscordActivityTimestamps 75 | _fields_ = [ 76 | ("Start", "start", int), 77 | ("End", "end", int) 78 | ] 79 | 80 | class ActivityAssets(Model): 81 | _struct_ = sdk.DiscordActivityAssets 82 | _fields_ = [ 83 | ("LargeImage", "large_image", str), 84 | ("LargeText", "large_text", str), 85 | ("SmallImage", "small_image", str), 86 | ("SmallText", "small_text", str) 87 | ] 88 | 89 | class PartySize(Model): 90 | _struct_ = sdk.DiscordPartySize 91 | _fields_ = [ 92 | ("CurrentSize", "current_size", int), 93 | ("MaxSize", "max_size", int) 94 | ] 95 | 96 | class ActivityParty(Model): 97 | _struct_ = sdk.DiscordActivityParty 98 | _fields_ = [ 99 | ("Id", "id", str), 100 | ("Size", "size", PartySize), 101 | ("Privacy", "privacy", ActivityPartyPrivacy) 102 | ] 103 | 104 | class ActivitySecrets(Model): 105 | _struct_ = sdk.DiscordActivitySecrets 106 | _fields_ = [ 107 | ("Match", "match", str), 108 | ("Join", "join", str), 109 | ("Spectate", "spectate", str) 110 | ] 111 | 112 | class Activity(Model): 113 | _struct_ = sdk.DiscordActivity 114 | _fields_ = [ 115 | ("ApplicationId", "application_id", int), 116 | ("Name", "name", str), 117 | ("State", "state", str), 118 | ("Details", "details", str), 119 | ("Timestamps", "timestamps", ActivityTimestamps), 120 | ("Assets", "assets", ActivityAssets), 121 | ("Party", "party", ActivityParty), 122 | ("Secrets", "secrets", ActivitySecrets), 123 | ("Instance", "instance", bool), 124 | ("SupportedPlatforms", "supported_platforms", ActivitySupportedPlatformFlags) 125 | ] 126 | 127 | class Presence(Model): 128 | _struct_ = sdk.DiscordPresence 129 | _fields_ = [ 130 | ("Status", "status", Status), 131 | ("Activity", "activity", Activity) 132 | ] 133 | 134 | class Relationship(Model): 135 | _struct_ = sdk.DiscordRelationship 136 | _fields_ = [ 137 | ("Type", "type", RelationshipType), 138 | ("User", "user", User), 139 | ("Presence", "presence", Presence) 140 | ] 141 | 142 | 143 | class ImageDimensions(Model): 144 | _struct_ = sdk.DiscordImageDimensions 145 | _fields_ = [ 146 | ("Width", "width", int), 147 | ("Height", "height", int) 148 | ] 149 | 150 | class ImageHandle(Model): 151 | _struct_ = sdk.DiscordImageHandle 152 | _fields_ = [ 153 | ("Type", "type", ImageType), 154 | ("Id", "id", int), 155 | ("Size", "size", int) 156 | ] 157 | 158 | class OAuth2Token(Model): 159 | _struct_ = sdk.DiscordOAuth2Token 160 | _fields_ = [ 161 | ("AccessToken", "access_token", str), 162 | ("Scopes", "scopes", str), 163 | ("Expires", "expires", int) 164 | ] 165 | 166 | class Lobby(Model): 167 | _struct_ = sdk.DiscordLobby 168 | _fields_ = [ 169 | ("Id", "id", int), 170 | ("Type", "type", LobbyType), 171 | ("OwnerId", "owner_id", int), 172 | ("Secret", "secret", str), 173 | ("Capacity", "capacity", int), 174 | ("Locked", "locked", bool) 175 | ] 176 | 177 | class InputMode(Model): 178 | _struct_ = sdk.DiscordInputMode 179 | _fields_ = [ 180 | ("Type", "type", InputModeType), 181 | ("Shortcut", "shortcut", str) 182 | ] 183 | 184 | class FileStat(Model): 185 | _struct_ = sdk.DiscordFileStat 186 | _fields_ = [ 187 | ("Filename", "filename", str), 188 | ("Size", "size", int), 189 | ("LastModified", "last_modified", int) 190 | ] 191 | 192 | class UserAchievement(Model): 193 | _struct_ = sdk.DiscordUserAchievement 194 | _fields_ = [ 195 | ("UserId", "user_id", str), 196 | ("AchievementId", "achievement_id", int), 197 | ("PercentComplete", "percent_complete", int), 198 | ("UnlockedAt", "unlocked_at", str) 199 | ] 200 | 201 | class SkuPrice(Model): 202 | _struct_ = sdk.DiscordSkuPrice 203 | _fields_ = [ 204 | ("Amount", "amount", int), 205 | ("Currency", "currency", str) 206 | ] 207 | 208 | class Sku(Model): 209 | _struct_ = sdk.DiscordSku 210 | _fields_ = [ 211 | ("Id", "id", int), 212 | ("Type", "type", SkuType), 213 | ("Name", "name", str), 214 | ("Price", "price", SkuPrice) 215 | ] 216 | 217 | class Entitlement(Model): 218 | _struct_ = sdk.DiscordEntitlement 219 | _fields_ = [ 220 | ("Id", "id", int), 221 | ("Type", "type", EntitlementType), 222 | ("SkuId", "sku_id", int) 223 | ] 224 | 225 | class ImeUnderline(Model): 226 | _struct_ = sdk.DiscordImeUnderline 227 | _fields_ = [ 228 | ("From", "from_", int), 229 | ("To", "to", int), 230 | ("Color", "color", int), 231 | ("BackgroundColor", "background_color", int), 232 | ("Thick", "thick", bool) 233 | ] 234 | 235 | class Rect(Model): 236 | _struct_ = sdk.DiscordRect 237 | _fields_ = [ 238 | ("Left", "left", int), 239 | ("Top", "top", int), 240 | ("Right", "right", int), 241 | ("Bottom", "bottom", int) 242 | ] -------------------------------------------------------------------------------- /discord/network.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .enum import Result 3 | from .exception import getException 4 | from .event import bindEvents 5 | import ctypes 6 | 7 | class NetworkManager: 8 | def __init__(self): 9 | self._internal = None 10 | self._events = bindEvents(sdk.IDiscordNetworkEvents, 11 | self._OnMessage, 12 | self._OnRouteUpdate 13 | ) 14 | 15 | def _OnMessage(self, event_data, peer_id, channel_id, data, data_length): 16 | data = bytes(data[:data_length]) 17 | self.OnMessage(peer_id, channel_id, data) 18 | 19 | def _OnRouteUpdate(self, event_data, route_data): 20 | self.OnRouteUpdate(route_data.decode("utf8")) 21 | 22 | def GetPeerId(self) -> int: 23 | """ 24 | Get the networking peer ID for the current user, allowing other users to send packets to them. 25 | """ 26 | peerId = sdk.DiscordNetworkPeerId() 27 | self._internal.get_peer_id(self._internal, peerId) 28 | return peerId.value 29 | 30 | def Flush(self) -> None: 31 | """ 32 | Flushes the network 33 | """ 34 | result = Result(self._internal.flush(self._internal)) 35 | if result != Result.Ok: 36 | raise getException(result) 37 | 38 | def OpenChannel(self, peerId: int, channelId: int, reliable: bool) -> None: 39 | """ 40 | Opens a channel to a user with their given peer ID on the given channel number. 41 | """ 42 | result = Result(self._internal.open_channel(self._internal, peerId, channelId, reliable)) 43 | if result != Result.Ok: 44 | raise getException(result) 45 | 46 | def OpenPeer(self, peerId: int, route: str) -> None: 47 | """ 48 | Opens a network connection to another Discord user. 49 | """ 50 | route_data = ctypes.create_string_buffer(route.encode("utf8")) 51 | result = Result(self._internal.open_peer(self._internal, peerId, route_data)) 52 | if result != Result.Ok: 53 | raise getException(result) 54 | 55 | def UpdatePeer(self, peerId: int, route: str) -> None: 56 | """ 57 | Updates the network connection to another Discord user. 58 | """ 59 | route_data = ctypes.create_string_buffer(route.encode("utf8")) 60 | result = Result(self._internal.update_peer(self._internal, peerId, route_datagit)) 61 | if result != Result.Ok: 62 | raise getException(result) 63 | 64 | def SendMessage(self, peerId: int, channelId: int, data: bytes) -> None: 65 | """ 66 | Sends data to a given peer ID through the given channel. 67 | """ 68 | data = (ctypes.c_uint8 * len(data))(*data) 69 | result = Result(self._internal.send_message(self._internal, peerId, channelId, data, len(data))) 70 | if result != Result.Ok: 71 | raise getException(result) 72 | 73 | def CloseChannel(self, peerId: int, channelId: int) -> None: 74 | """ 75 | Close the connection to a given user by peerId on the given channel. 76 | """ 77 | result = Result(self._internal.close_channel(self._internal, peerId, channelId)) 78 | if result != Result.Ok: 79 | raise getException(result) 80 | 81 | def ClosePeer(self, peerId: int) -> None: 82 | """ 83 | Disconnects the network session to another Discord user. 84 | """ 85 | result = Result(self._internal.close_peer(self._internal, peerId)) 86 | if result != Result.Ok: 87 | raise getException(result) 88 | 89 | def OnMessage(self, peerId: int, channelId: int, data: bytes) -> None: 90 | """ 91 | Fires when you receive data from another user. 92 | """ 93 | pass 94 | 95 | def OnRouteUpdate(self, route: str) -> None: 96 | """ 97 | Fires when your networking route has changed. 98 | """ 99 | pass 100 | -------------------------------------------------------------------------------- /discord/overlay.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import Rect, ImeUnderline 3 | from .enum import Result, ActivityActionType, KeyVariant, MouseButton 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable 7 | import ctypes 8 | 9 | class OverlayManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordOverlayEvents, 14 | self._OnToggle 15 | ) 16 | 17 | def _OnToggle(self, event_data, locked): 18 | self.OnToggle(locked) 19 | 20 | def IsEnabled(self) -> bool: 21 | """ 22 | Check whether the user has the overlay enabled or disabled. 23 | """ 24 | enabled = ctypes.c_bool() 25 | self._internal.is_enabled(self._internal, enabled) 26 | return enabled.value 27 | 28 | def IsLocked(self) -> bool: 29 | """ 30 | Check if the overlay is currently locked or unlocked 31 | """ 32 | locked = ctypes.c_bool() 33 | self._internal.is_locked(self._internal, locked) 34 | return locked.value 35 | 36 | def SetLocked(self, locked: bool, callback: Callable[[Result], None]) -> None: 37 | """ 38 | Locks or unlocks input in the overlay. 39 | """ 40 | def CCallback(event_data, result): 41 | self._garbage.remove(CCallback) 42 | result = Result(result) 43 | callback(result) 44 | 45 | CCallback = self._internal.set_locked.argtypes[-1](CCallback) 46 | self._garbage.append(CCallback) # prevent it from being garbage collected 47 | 48 | self._internal.set_locked(self._internal, locked, ctypes.c_void_p(), CCallback) 49 | 50 | def OpenActivityInvite(self, type: ActivityActionType, callback: Callable[[Result], None]) -> None: 51 | """ 52 | Opens the overlay modal for sending game invitations to users, channels, and servers. 53 | """ 54 | def CCallback(event_data, result): 55 | self._garbage.remove(CCallback) 56 | result = Result(result) 57 | callback(result) 58 | 59 | CCallback = self._internal.open_activity_invite.argtypes[-1](CCallback) 60 | self._garbage.append(CCallback) # prevent it from being garbage collected 61 | 62 | self._internal.open_activity_invite(self._internal, type, ctypes.c_void_p(), CCallback) 63 | 64 | def OpenGuildInvite(self, code: str, callback: Callable[[Result], None]) -> None: 65 | """ 66 | Opens the overlay modal for joining a Discord guild, given its invite code. 67 | """ 68 | def CCallback(event_data, result): 69 | self._garbage.remove(CCallback) 70 | result = Result(result) 71 | callback(result) 72 | 73 | CCallback = self._internal.open_guild_invite.argtypes[-1](CCallback) 74 | self._garbage.append(CCallback) # prevent it from being garbage collected 75 | 76 | code = ctypes.c_char_p(code.encode("utf8")) 77 | self._internal.open_guild_invite(self._internal, code, ctypes.c_void_p(), CCallback) 78 | 79 | def OpenVoiceSettings(self, callback: Callable[[Result], None]) -> None: 80 | """ 81 | Opens the overlay widget for voice settings for the currently connected application. 82 | """ 83 | def CCallback(event_data, result): 84 | self._garbage.remove(CCallback) 85 | result = Result(result) 86 | callback(result) 87 | 88 | CCallback = self._internal.open_voice_settings.argtypes[-1](CCallback) 89 | self._garbage.append(CCallback) # prevent it from being garbage collected 90 | 91 | self._internal.open_voice_settings(self._internal, ctypes.c_void_p(), CCallback) 92 | 93 | def OnToggle(self, locked: bool) -> None: 94 | """ 95 | Fires when the overlay is locked or unlocked (a.k.a. opened or closed) 96 | """ 97 | pass 98 | 99 | # 100 | # The followings function are not documented. 101 | # Their documentation will be added as the official updates. 102 | # 103 | 104 | def InitDrawingDxgi(self, swapchain: ctypes.c_void_p, useMessageForwarding: bool) -> None: 105 | result = Result(self._internal.init_drawing_dxgi(self._internal, swapchain, useMessageForwarding)) 106 | if result != Result.Ok: 107 | raise getException(result) 108 | 109 | def OnPresent(self): 110 | self._internal.on_present(self._internal) 111 | 112 | def ForwardMessage(self, message: ctypes.c_void_p) -> None: 113 | self._internal.forward_message(self._internal, message) 114 | 115 | def KeyEvent(self, down: bool, keyCode: str, variant: KeyVariant) -> None: 116 | keyCode = ctypes.c_char_p(keyCode.encode("utf8")) 117 | self._internal.key_event(self._internal, down, keyCode, variant) 118 | 119 | def CharEvent(self, character: str) -> None: 120 | character = ctypes.c_char_p(character.encode("utf8")) 121 | self._internal.char_event(self._internal, character) 122 | 123 | def MouseButtonEvent(self, down: int, clickCount: int, which: MouseButton, x: int, y: int) -> None: 124 | self._internal.mouse_button_event(self._internal, down, clickCount, which, x, y) 125 | 126 | def MouseMotionEvent(self, x: int, y: int) -> None: 127 | self._internal.mouse_motion_event(self._internal, x, y) 128 | 129 | def ImeCommitText(self, text: str) -> None: 130 | text = ctypes.c_char_p(text.encode("utf8")) 131 | self._internal.ime_commit_text(self._internal, text) 132 | 133 | def ImeSetComposition(self, text: str, underlines: ImeUnderline, from_: int, to: int) -> None: 134 | text = ctypes.c_char_p(text.encode("utf8")) 135 | self._internal.ime_set_composition(self._internal, text, underlines._internal, ctypes.sizeof(underlines._internal), from_, to) # TODO: check sizeof argument 136 | 137 | def ImeCancelComposition(self) -> None: 138 | self._internal.ime_cancel_composition(self._internal) 139 | 140 | def SetImeCompositionRangeCallback(self, callback: Callable[[int, int, Rect], None]) -> None: 141 | def CCallback(on_ime_composition_range_changed_data, from_, to, bounds, bounds_length): 142 | self._garbage.remove(CCallback) 143 | callback(from_, to, Rect(copy = bounds)) 144 | 145 | CCallback = self._internal.set_ime_composition_range_callback.argtypes[-1](CCallback) 146 | self._garbage.append(CCallback) # prevent it from being garbage collected 147 | 148 | self._internal.set_ime_composition_range_callback(self._internal, ctypes.c_void_p(), CCallback) 149 | 150 | def SetImeSelectionBoundsCallback(self, callback: Callable[[Rect, Rect, bool], None]) -> None: 151 | def CCallback(on_ime_selection_bounds_changed_data, anchor, focus, is_anchor_first): 152 | self._garbage.remove(CCallback) 153 | callback(Rect(internal = anchor), Rect(internal = focus), is_anchor_first) 154 | 155 | CCallback = self._internal.set_ime_selection_bounds_callback.argtypes[-1](CCallback) 156 | self._garbage.append(CCallback) # prevent it from being garbage collected 157 | 158 | self._internal.set_ime_selection_bounds_callback(self._internal, ctypes.c_void_p(), CCallback) 159 | 160 | def IsPointInsideClickZone(self, x: int, y: int) -> None: 161 | point_inside_click_zone = ctypes.c_bool() 162 | self._internal.is_point_inside_click_zone(self._internal, x, y) 163 | return point_inside_click_zone.value -------------------------------------------------------------------------------- /discord/relationship.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import Relationship 3 | from .enum import Result 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable 7 | import ctypes 8 | 9 | class RelationshipManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordRelationshipEvents, 14 | self._OnRefresh, 15 | self._OnRelationshipUpdate 16 | ) 17 | 18 | def _OnRefresh(self, event_data): 19 | self.OnRefresh() 20 | 21 | def _OnRelationshipUpdate(self, event_data, relationship): 22 | self.OnRelationshipUpdate(Relationship(copy = relationship.contents)) 23 | 24 | def Filter(self, filter: Callable[[Relationship], None]) -> None: 25 | """ 26 | Filters a user's relationship list by a boolean condition. 27 | """ 28 | def CFilter(filter_data, relationship): 29 | return bool(filter(Relationship(copy = relationship.contents))) 30 | 31 | CFilter = self._internal.filter.argtypes[-1](CFilter) 32 | 33 | self._internal.filter(self._internal, ctypes.c_void_p(), CFilter) 34 | 35 | def Get(self, userId: int) -> Relationship: 36 | """ 37 | Get the relationship between the current user and a given user by id. 38 | """ 39 | pointer = sdk.DiscordRelationship() 40 | result = Result(self._internal.get(self._internal, userId, pointer)) 41 | if result != Result.Ok: 42 | raise getException(result) 43 | 44 | return Relationship(internal = pointer) 45 | 46 | def GetAt(self, index: int) -> Relationship: 47 | """ 48 | Get the relationship at a given index when iterating over a list of relationships. 49 | """ 50 | pointer = sdk.DiscordRelationship() 51 | result = Result(self._internal.get_at(self._internal, index, pointer)) 52 | if result != Result.Ok: 53 | raise getException(result) 54 | 55 | return Relationship(internal = pointer) 56 | 57 | def Count(self) -> int: 58 | """ 59 | Get the number of relationships that match your filter. 60 | """ 61 | count = ctypes.c_int32() 62 | result = Result(self._internal.count(self._internal, count)) 63 | if result != Result.Ok: 64 | raise getException(result) 65 | 66 | return count.value 67 | 68 | def OnRefresh(self) -> None: 69 | """ 70 | Fires at initialization when Discord has cached a snapshot of the current status of all your relationships. 71 | """ 72 | pass 73 | 74 | def OnRelationshipUpdate(self, relationship: Relationship) -> None: 75 | """ 76 | Fires when a relationship in the filtered list changes, like an updated presence or user attribute. 77 | """ 78 | pass -------------------------------------------------------------------------------- /discord/sdk.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import os.path 3 | 4 | DiscordClientId = c_int64 5 | DiscordVersion = c_int32 6 | DiscordSnowflake = c_int64 7 | DiscordTimestamp = c_int64 8 | DiscordUserId = DiscordSnowflake 9 | DiscordLocale = c_char * 128 10 | DiscordBranch = c_char * 4096 11 | DiscordLobbyId = DiscordSnowflake 12 | DiscordLobbySecret = c_char * 128 13 | DiscordMetadataKey = c_char * 256 14 | DiscordMetadataValue = c_char * 4096 15 | DiscordNetworkPeerId = c_uint64 16 | DiscordNetworkChannelId = c_uint8 17 | DiscordPath = c_char * 4096 18 | DiscordDateTime = c_char * 64 19 | 20 | class DiscordUser(Structure): 21 | _fields_ = [ 22 | ("id", DiscordUserId), 23 | ("username", c_char * 256), 24 | ("discriminator", c_char * 8), 25 | ("avatar", c_char * 128), 26 | ("bot", c_bool) 27 | ] 28 | 29 | class DiscordOAuth2Token(Structure): 30 | _fields_ = [ 31 | ("access_token", c_char * 128), 32 | ("scopes", c_char * 1024), 33 | ("expires", DiscordTimestamp) 34 | ] 35 | 36 | class DiscordImageHandle(Structure): 37 | _fields_ = [ 38 | ("type", c_int32), 39 | ("id", c_int64), 40 | ("size", c_uint32) 41 | ] 42 | 43 | class DiscordImageDimensions(Structure): 44 | _fields_ = [ 45 | ("width", c_uint32), 46 | ("height", c_uint32), 47 | ] 48 | 49 | class DiscordActivityTimestamps(Structure): 50 | _fields_ = [ 51 | ("start", DiscordTimestamp), 52 | ("end", DiscordTimestamp) 53 | ] 54 | 55 | class DiscordActivityAssets(Structure): 56 | _fields_ = [ 57 | ("large_image", c_char * 128), 58 | ("large_text", c_char * 128), 59 | ("small_image", c_char * 128), 60 | ("small_text", c_char * 128) 61 | ] 62 | 63 | class DiscordPartySize(Structure): 64 | _fields_ = [ 65 | ("current_size", c_int32), 66 | ("max_size", c_int32) 67 | ] 68 | 69 | class DiscordActivityParty(Structure): 70 | _fields_ = [ 71 | ("id", c_char * 128), 72 | ("size", DiscordPartySize), 73 | ("privacy", c_int32) 74 | ] 75 | 76 | class DiscordActivitySecrets(Structure): 77 | _fields_ = [ 78 | ("match", c_char * 128), 79 | ("join", c_char * 128), 80 | ("spectate", c_char * 128) 81 | ] 82 | 83 | class DiscordActivity(Structure): 84 | _fields_ = [ 85 | ("type", c_int32), 86 | ("application_id", c_uint64), 87 | ("name", c_char * 128), 88 | ("state", c_char * 128), 89 | ("details", c_char * 128), 90 | ("timestamps", DiscordActivityTimestamps), 91 | ("assets", DiscordActivityAssets), 92 | ("party", DiscordActivityParty), 93 | ("secrets", DiscordActivitySecrets), 94 | ("instance", c_bool), 95 | ("supported_platforms", c_uint32) 96 | ] 97 | 98 | class DiscordPresence(Structure): 99 | _fields_ = [ 100 | ("status", c_int32), 101 | ("activity", DiscordActivity) 102 | ] 103 | 104 | class DiscordRelationship(Structure): 105 | _fields_ = [ 106 | ("type", c_int32), 107 | ("user", DiscordUser), 108 | ("presence", DiscordPresence) 109 | ] 110 | 111 | class DiscordLobby(Structure): 112 | _fields_ = [ 113 | ("id", DiscordLobbyId), 114 | ("type", c_int32), 115 | ("owner_id", DiscordUserId), 116 | ("secret", DiscordLobbySecret), 117 | ("capacity", c_uint32), 118 | ("locked", c_bool) 119 | ] 120 | 121 | class DiscordImeUnderline(Structure): 122 | _fields_ = [ 123 | ("from_", c_int32), 124 | ("to", c_int32), 125 | ("color", c_uint32), 126 | ("background_color", c_uint32), 127 | ("thick", c_bool) 128 | ] 129 | 130 | class DiscordRect(Structure): 131 | _fields_ = [ 132 | ("left", c_int32), 133 | ("top", c_int32), 134 | ("right", c_int32), 135 | ("bottom", c_int32) 136 | ] 137 | 138 | class DiscordFileStat(Structure): 139 | _fields_ = [ 140 | ("filename", c_char * 260), 141 | ("size", c_uint64), 142 | ("last_modified", c_uint64) 143 | ] 144 | 145 | class DiscordEntitlement(Structure): 146 | _fields_ = [ 147 | ("id", DiscordSnowflake), 148 | ("type", c_int32), 149 | ("sku_id", DiscordSnowflake) 150 | ] 151 | 152 | class DiscordSkuPrice(Structure): 153 | _fields_ = [ 154 | ("amount", c_uint32), 155 | ("currency", c_char * 16) 156 | ] 157 | 158 | class DiscordSku(Structure): 159 | _fields_ = [ 160 | ("id", DiscordSnowflake), 161 | ("type", c_int32), 162 | ("name", c_char * 256), 163 | ("price", DiscordSkuPrice) 164 | ] 165 | 166 | class DiscordInputMode(Structure): 167 | _fields_ = [ 168 | ("type", c_int32), 169 | ("shortcut", c_char * 256) 170 | ] 171 | 172 | class DiscordUserAchievement(Structure): 173 | _fields_ = [ 174 | ("user_id", DiscordSnowflake), 175 | ("achievement_id", DiscordSnowflake), 176 | ("percent_complete", c_uint8), 177 | ("unlocked_at", DiscordDateTime) 178 | ] 179 | 180 | class IDiscordLobbyTransaction(Structure): 181 | pass 182 | 183 | IDiscordLobbyTransaction._fields_ = [ 184 | ("set_type", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), c_int32)), 185 | ("set_owner", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), DiscordUserId)), 186 | ("set_capacity", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), c_uint32)), 187 | ("set_metadata", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), DiscordMetadataKey, DiscordMetadataValue)), 188 | ("delete_metadata", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), DiscordMetadataKey)), 189 | ("set_locked", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyTransaction), c_bool)), 190 | ] 191 | 192 | class IDiscordLobbyMemberTransaction(Structure): 193 | pass 194 | 195 | IDiscordLobbyMemberTransaction._fields_ = [ 196 | ("set_metadata", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyMemberTransaction), DiscordMetadataKey, DiscordMetadataValue)), 197 | ("delete_metadata", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyMemberTransaction), DiscordMetadataKey)) 198 | ] 199 | 200 | class IDiscordLobbySearchQuery(Structure): 201 | pass 202 | 203 | IDiscordLobbySearchQuery._fields_ = [ 204 | ("filter", CFUNCTYPE(c_int32, POINTER(IDiscordLobbySearchQuery), DiscordMetadataKey, c_int32, c_int32, DiscordMetadataValue)), 205 | ("sort", CFUNCTYPE(c_int32, POINTER(IDiscordLobbySearchQuery), DiscordMetadataKey, c_int32, DiscordMetadataValue)), 206 | ("limit", CFUNCTYPE(c_int32, POINTER(IDiscordLobbySearchQuery), c_uint32)), 207 | ("distance", CFUNCTYPE(c_int32, POINTER(IDiscordLobbySearchQuery), c_int32)) 208 | ] 209 | 210 | IDiscordApplicationEvents = c_void_p 211 | 212 | class IDiscordApplicationManager(Structure): 213 | pass 214 | 215 | IDiscordApplicationManager._fields_ = [ 216 | ("validate_or_exit", CFUNCTYPE(None, POINTER(IDiscordApplicationManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 217 | ("get_current_locale", CFUNCTYPE(None, POINTER(IDiscordApplicationManager), POINTER(DiscordLocale))), 218 | ("get_current_branch", CFUNCTYPE(None, POINTER(IDiscordApplicationManager), POINTER(DiscordBranch))), 219 | ("get_oauth2_token", CFUNCTYPE(None, POINTER(IDiscordApplicationManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordOAuth2Token)))), 220 | ("get_ticket", CFUNCTYPE(None, POINTER(IDiscordApplicationManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32, c_char_p))), 221 | ] 222 | 223 | class IDiscordUserEvents(Structure): 224 | _fields_ = [ 225 | ("on_current_user_update", CFUNCTYPE(None, c_void_p)) 226 | ] 227 | 228 | class IDiscordUserManager(Structure): 229 | pass 230 | 231 | IDiscordUserManager._fields_ = [ 232 | ("get_current_user", CFUNCTYPE(c_int32, POINTER(IDiscordUserManager), POINTER(DiscordUser))), 233 | ("get_user", CFUNCTYPE(None, POINTER(IDiscordUserManager), DiscordUserId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordUser)))), 234 | ("get_current_user_premium_type", CFUNCTYPE(c_int32, POINTER(IDiscordUserManager), POINTER(c_int32))), 235 | ("current_user_has_flag", CFUNCTYPE(c_int32, POINTER(IDiscordUserManager), c_int32, POINTER(c_bool))), 236 | ] 237 | 238 | IDiscordImageEvents = c_void_p 239 | 240 | class IDiscordImageManager(Structure): 241 | pass 242 | 243 | IDiscordImageManager._fields_ = [ 244 | ("fetch", CFUNCTYPE(None, POINTER(IDiscordImageManager), DiscordImageHandle, c_bool, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, DiscordImageHandle))), 245 | ("get_dimensions", CFUNCTYPE(c_int32, POINTER(IDiscordImageManager), DiscordImageHandle, POINTER(DiscordImageDimensions))), 246 | ("get_data", CFUNCTYPE(c_int32, POINTER(IDiscordImageManager), DiscordImageHandle, POINTER(c_uint8), c_uint32)), 247 | ] 248 | 249 | class IDiscordActivityEvents(Structure): 250 | _fields_ = [ 251 | ("on_activity_join", CFUNCTYPE(None, c_void_p, c_char_p)), 252 | ("on_activity_spectate", CFUNCTYPE(None, c_void_p, c_char_p)), 253 | ("on_activity_join_request", CFUNCTYPE(None, c_void_p, POINTER(DiscordUser))), 254 | ("on_activity_invite", CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordUser), POINTER(DiscordActivity))) 255 | ] 256 | 257 | class IDiscordActivityManager(Structure): 258 | pass 259 | 260 | IDiscordActivityManager._fields_ = [ 261 | ("register_command", CFUNCTYPE(c_int32, POINTER(IDiscordActivityManager), c_char_p)), 262 | ("register_steam", CFUNCTYPE(c_int32, POINTER(IDiscordActivityManager), c_int32)), 263 | ("update_activity", CFUNCTYPE(None, POINTER(IDiscordActivityManager), POINTER(DiscordActivity), c_void_p, CFUNCTYPE(None, c_void_p, c_uint32))), 264 | ("clear_activity", CFUNCTYPE(None, POINTER(IDiscordActivityManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 265 | ("send_request_reply", CFUNCTYPE(None, POINTER(IDiscordActivityManager), DiscordUserId, c_int32, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 266 | ("send_invite", CFUNCTYPE(None, POINTER(IDiscordActivityManager), DiscordUserId, c_int32, c_char_p, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 267 | ("accept_invite", CFUNCTYPE(None, POINTER(IDiscordActivityManager), DiscordUserId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))) 268 | ] 269 | class IDiscordRelationshipEvents(Structure): 270 | _fields_ = [ 271 | ("on_refresh", CFUNCTYPE(None, c_void_p)), 272 | ("on_relationship_update", CFUNCTYPE(None, c_void_p, POINTER(DiscordRelationship))) 273 | ] 274 | 275 | class IDiscordRelationshipManager(Structure): 276 | pass 277 | 278 | IDiscordRelationshipManager._fields_ = [ 279 | ("filter", CFUNCTYPE(None, POINTER(IDiscordRelationshipManager), c_void_p, CFUNCTYPE(c_bool, c_void_p, POINTER(DiscordRelationship)))), 280 | ("count", CFUNCTYPE(c_int32, POINTER(IDiscordRelationshipManager), POINTER(c_int32))), 281 | ("get", CFUNCTYPE(c_int32, POINTER(IDiscordRelationshipManager), DiscordUserId, POINTER(DiscordRelationship))), 282 | ("get_at", CFUNCTYPE(c_int32, POINTER(IDiscordRelationshipManager), c_uint32, POINTER(DiscordRelationship))) 283 | ] 284 | 285 | class IDiscordLobbyEvents(Structure): 286 | _fields_ = [ 287 | ("on_lobby_update", CFUNCTYPE(None, c_void_p, c_int64)), 288 | ("on_lobby_delete", CFUNCTYPE(None, c_void_p, c_int64, c_uint32)), 289 | ("on_member_connect", CFUNCTYPE(None, c_void_p, c_int64, c_int64)), 290 | ("on_member_update", CFUNCTYPE(None, c_void_p, c_int64, c_int64)), 291 | ("on_member_disconnect", CFUNCTYPE(None, c_void_p, c_int64, c_int64)), 292 | ("on_lobby_message", CFUNCTYPE(None, c_void_p, c_int64, c_int64, POINTER(c_uint8), c_uint32)), 293 | ("on_speaking", CFUNCTYPE(None, c_void_p, c_int64, c_int64, c_bool)), 294 | ("on_network_message", CFUNCTYPE(None, c_void_p, c_int64, c_int64, c_uint8, POINTER(c_uint8), c_uint32)) 295 | ] 296 | 297 | class IDiscordLobbyManager(Structure): 298 | pass 299 | 300 | IDiscordLobbyManager._fields_ = [ 301 | ("get_lobby_create_transaction", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), POINTER(POINTER(IDiscordLobbyTransaction)))), 302 | ("get_lobby_update_transaction", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(POINTER(IDiscordLobbyTransaction)))), 303 | ("get_member_update_transaction", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, POINTER(POINTER(IDiscordLobbyMemberTransaction)))), 304 | ("create_lobby", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), POINTER(IDiscordLobbyTransaction), c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordLobby)))), 305 | ("update_lobby", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(IDiscordLobbyTransaction), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 306 | ("delete_lobby", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 307 | ("connect_lobby", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordLobbySecret, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordLobby)))), 308 | ("connect_lobby_with_activity_secret", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbySecret, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(DiscordLobby)))), 309 | ("disconnect_lobby", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 310 | ("get_lobby", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(DiscordLobby))), 311 | ("get_lobby_activity_secret", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(DiscordLobbySecret))), 312 | ("get_lobby_metadata_value", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordMetadataKey, POINTER(DiscordMetadataValue))), 313 | ("get_lobby_metadata_key", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_int32, POINTER(DiscordMetadataKey))), 314 | ("lobby_metadata_count", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(c_int32))), 315 | ("member_count", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(c_int32))), 316 | ("get_member_user_id", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_int32, POINTER(DiscordUserId))), 317 | ("get_member_user", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, POINTER(DiscordUser))), 318 | ("get_member_metadata_value", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, DiscordMetadataKey, POINTER(DiscordMetadataValue))), 319 | ("get_member_metadata_key", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, c_int32, POINTER(DiscordMetadataKey))), 320 | ("member_metadata_count", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, POINTER(c_int32))), 321 | ("update_member", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, POINTER(IDiscordLobbyMemberTransaction), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 322 | ("send_lobby_message", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, POINTER(c_uint8), c_uint32, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 323 | ("get_search_query", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), POINTER(POINTER(IDiscordLobbySearchQuery)))), 324 | ("search", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), POINTER(IDiscordLobbySearchQuery), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 325 | ("lobby_count", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), POINTER(c_int32))), 326 | ("get_lobby_id", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), c_int32, POINTER(DiscordLobbyId))), 327 | ("connect_voice", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 328 | ("disconnect_voice", CFUNCTYPE(None, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 329 | ("connect_network", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId)), 330 | ("disconnect_network", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId)), 331 | ("flush_network", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager))), 332 | ("open_network_channel", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, c_uint8, c_bool)), 333 | ("send_network_message", CFUNCTYPE(c_int32, POINTER(IDiscordLobbyManager), DiscordLobbyId, DiscordUserId, c_uint8, POINTER(c_uint8), c_uint32)), 334 | ] 335 | 336 | class IDiscordNetworkEvents(Structure): 337 | _fields_ = [ 338 | ("on_message", CFUNCTYPE(None, c_void_p, DiscordNetworkPeerId, DiscordNetworkChannelId, POINTER(c_uint8), c_uint32)), 339 | ("on_route_update", CFUNCTYPE(None, c_void_p, c_char_p)) 340 | ] 341 | 342 | class IDiscordNetworkManager(Structure): 343 | pass 344 | 345 | IDiscordNetworkManager._fields_ = [ 346 | ("get_peer_id", CFUNCTYPE(None, POINTER(IDiscordNetworkManager), POINTER(DiscordNetworkPeerId))), 347 | ("flush", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager))), 348 | ("open_peer", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId, c_char_p)), 349 | ("update_peer", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId, c_char_p)), 350 | ("close_peer", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId)), 351 | ("open_channel", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId, DiscordNetworkChannelId, c_bool)), 352 | ("close_channel", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId, DiscordNetworkChannelId)), 353 | ("send_message", CFUNCTYPE(c_int32, POINTER(IDiscordNetworkManager), DiscordNetworkPeerId, DiscordNetworkChannelId, POINTER(c_uint8), c_uint32)) 354 | ] 355 | 356 | class IDiscordOverlayEvents(Structure): 357 | _fields_ = [ 358 | ("on_toggle", CFUNCTYPE(None, c_void_p, c_bool)) 359 | ] 360 | 361 | class IDiscordOverlayManager(Structure): 362 | pass 363 | 364 | IDiscordOverlayManager._fields_ = [ 365 | ("is_enabled", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), POINTER(c_bool))), 366 | ("is_locked", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), POINTER(c_bool))), 367 | ("set_locked", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_bool, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 368 | ("open_activity_invite", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_int32, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 369 | ("open_guild_invite", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_char_p, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 370 | ("open_voice_settings", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 371 | 372 | ("init_drawing_dxgi", CFUNCTYPE(c_int32, POINTER(IDiscordOverlayManager), c_void_p, c_bool)), 373 | ("on_present", CFUNCTYPE(None, POINTER(IDiscordOverlayManager))), 374 | ("forward_message", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_void_p)), 375 | ("key_event", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_bool, c_char_p, c_int32)), 376 | ("char_event", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_char_p)), 377 | ("mouse_button_event", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_uint8, c_int32, c_int32, c_int32, c_int32)), 378 | ("mouse_motion_event", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_int32, c_int32)), 379 | ("ime_commit_text", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_char_p)), 380 | ("ime_set_composition", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_char_p, POINTER(DiscordImeUnderline), c_uint32, c_int32, c_int32)), 381 | ("ime_cancel_composition", CFUNCTYPE(None, POINTER(IDiscordOverlayManager))), 382 | ("set_ime_composition_range_callback", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32, c_int32, POINTER(DiscordRect), c_uint32))), 383 | ("set_ime_selection_bounds_callback", CFUNCTYPE(None, POINTER(IDiscordOverlayManager), c_void_p, CFUNCTYPE(None, DiscordRect, DiscordRect, c_bool))), 384 | ("is_point_inside_click_zone", CFUNCTYPE(c_bool, POINTER(IDiscordOverlayManager), c_int32, c_int32)) 385 | ] 386 | 387 | IDiscordStorageEvents = c_void_p 388 | 389 | class IDiscordStorageManager(Structure): 390 | pass 391 | 392 | IDiscordStorageManager._fields_ = [ 393 | ("read", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_char_p, POINTER(c_uint8), c_uint32, POINTER(c_uint32))), 394 | ("read_async", CFUNCTYPE(None, POINTER(IDiscordStorageManager), c_char_p, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(c_uint8), c_uint32))), 395 | ("read_async_partial", CFUNCTYPE(None, POINTER(IDiscordStorageManager), c_char_p, c_uint64, c_uint64, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, POINTER(c_uint8), c_uint32))), 396 | ("write", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_char_p, POINTER(c_uint8), c_uint32)), 397 | ("write_async", CFUNCTYPE(None, POINTER(IDiscordStorageManager), c_char_p, POINTER(c_uint8), c_uint32, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 398 | ("delete_", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_char_p)), 399 | ("exists", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_char_p, POINTER(c_bool))), 400 | ("count", CFUNCTYPE(None, POINTER(IDiscordStorageManager), POINTER(c_int32))), 401 | ("stat", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_char_p, POINTER(DiscordFileStat))), 402 | ("stat_at", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), c_int32, POINTER(DiscordFileStat))), 403 | ("get_path", CFUNCTYPE(c_int32, POINTER(IDiscordStorageManager), POINTER(DiscordPath))) 404 | ] 405 | 406 | class IDiscordStoreEvents(Structure): 407 | _fields_ = [ 408 | ("on_entitlement_create", CFUNCTYPE(None, c_void_p, POINTER(DiscordEntitlement))), 409 | ("on_entitlement_delete", CFUNCTYPE(None, c_void_p, POINTER(DiscordEntitlement))) 410 | ] 411 | 412 | class IDiscordStoreManager(Structure): 413 | pass 414 | 415 | IDiscordStoreManager._fields_ = [ 416 | ("fetch_skus", CFUNCTYPE(None, POINTER(IDiscordStoreManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 417 | ("count_skus", CFUNCTYPE(None, POINTER(IDiscordStoreManager), POINTER(c_int32))), 418 | ("get_sku", CFUNCTYPE(c_int32, POINTER(IDiscordStoreManager), DiscordSnowflake, POINTER(DiscordSku))), 419 | ("get_sku_at", CFUNCTYPE(c_int32, POINTER(IDiscordStoreManager), c_int32, POINTER(DiscordSku))), 420 | ("fetch_entitlements", CFUNCTYPE(None, POINTER(IDiscordStoreManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 421 | ("count_entitlements", CFUNCTYPE(None, POINTER(IDiscordStoreManager), POINTER(c_int32))), 422 | ("get_entitlement", CFUNCTYPE(c_int32, POINTER(IDiscordStoreManager), DiscordSnowflake, POINTER(DiscordEntitlement))), 423 | ("get_entitlement_at", CFUNCTYPE(c_int32, POINTER(IDiscordStoreManager), c_int32, POINTER(DiscordEntitlement))), 424 | ("has_sku_entitlement", CFUNCTYPE(c_int32, POINTER(IDiscordStoreManager), DiscordSnowflake, POINTER(c_bool))), 425 | ("start_purchase", CFUNCTYPE(None, POINTER(IDiscordStoreManager), DiscordSnowflake, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))) 426 | ] 427 | 428 | class IDiscordVoiceEvents(Structure): 429 | _fields_ = [ 430 | ("on_settings_update", CFUNCTYPE(None, c_void_p)) 431 | ] 432 | 433 | class IDiscordVoiceManager(Structure): 434 | pass 435 | 436 | IDiscordVoiceManager._fields_ = [ 437 | ("get_input_mode", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), POINTER(DiscordInputMode))), 438 | ("set_input_mode", CFUNCTYPE(None, POINTER(IDiscordVoiceManager), DiscordInputMode, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 439 | ("is_self_mute", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), POINTER(c_bool))), 440 | ("set_self_mute", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), c_bool)), 441 | ("is_self_deaf", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), POINTER(c_bool))), 442 | ("set_self_deaf", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), c_bool)), 443 | ("is_local_mute", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), DiscordSnowflake, POINTER(c_bool))), 444 | ("set_local_mute", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), DiscordSnowflake, c_bool)), 445 | ("get_local_volume", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), DiscordSnowflake, POINTER(c_uint8))), 446 | ("set_local_volume", CFUNCTYPE(c_int32, POINTER(IDiscordVoiceManager), DiscordSnowflake, c_uint8)) 447 | ] 448 | 449 | class IDiscordAchievementEvents(Structure): 450 | _fields_ = [ 451 | ("on_user_achievement_update", CFUNCTYPE(None, c_void_p, POINTER(DiscordUserAchievement))) 452 | ] 453 | 454 | class IDiscordAchievementManager(Structure): 455 | pass 456 | 457 | IDiscordAchievementManager._fields_ = [ 458 | ("set_user_achievement", CFUNCTYPE(None, POINTER(IDiscordAchievementManager), DiscordSnowflake, c_uint8, c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 459 | ("fetch_user_achievements", CFUNCTYPE(None, POINTER(IDiscordAchievementManager), c_void_p, CFUNCTYPE(None, c_void_p, c_int32))), 460 | ("count_user_achievements", CFUNCTYPE(None, POINTER(IDiscordAchievementManager), POINTER(c_int32))), 461 | ("get_user_achievement", CFUNCTYPE(c_int32, POINTER(IDiscordAchievementManager), DiscordSnowflake, POINTER(DiscordUserAchievement))), 462 | ("get_user_achievement_at", CFUNCTYPE(c_int32, POINTER(IDiscordAchievementManager), c_int32, POINTER(DiscordUserAchievement))) 463 | ] 464 | 465 | IDiscordCoreEvents = c_void_p 466 | 467 | class IDiscordCore(Structure): 468 | pass 469 | 470 | IDiscordCore._fields_ = [ 471 | ("destroy", CFUNCTYPE(None, POINTER(IDiscordCore))), 472 | ("run_callbacks", CFUNCTYPE(c_int32, POINTER(IDiscordCore))), 473 | ("set_log_hook", CFUNCTYPE(None, POINTER(IDiscordCore), c_int32, c_void_p, CFUNCTYPE(None, c_void_p, c_int32, c_char_p))), 474 | ("get_application_manager", CFUNCTYPE(POINTER(IDiscordApplicationManager), POINTER(IDiscordCore))), 475 | ("get_user_manager", CFUNCTYPE(POINTER(IDiscordUserManager), POINTER(IDiscordCore))), 476 | ("get_image_manager", CFUNCTYPE(POINTER(IDiscordImageManager), POINTER(IDiscordCore))), 477 | ("get_activity_manager", CFUNCTYPE(POINTER(IDiscordActivityManager), POINTER(IDiscordCore))), 478 | ("get_relationship_manager", CFUNCTYPE(POINTER(IDiscordRelationshipManager), POINTER(IDiscordCore))), 479 | ("get_lobby_manager", CFUNCTYPE(POINTER(IDiscordLobbyManager), POINTER(IDiscordCore))), 480 | ("get_network_manager", CFUNCTYPE(POINTER(IDiscordNetworkManager), POINTER(IDiscordCore))), 481 | ("get_overlay_manager", CFUNCTYPE(POINTER(IDiscordOverlayManager), POINTER(IDiscordCore))), 482 | ("get_storage_manager", CFUNCTYPE(POINTER(IDiscordStorageManager), POINTER(IDiscordCore))), 483 | ("get_store_manager", CFUNCTYPE(POINTER(IDiscordStoreManager), POINTER(IDiscordCore))), 484 | ("get_voice_manager", CFUNCTYPE(POINTER(IDiscordVoiceManager), POINTER(IDiscordCore))), 485 | ("get_achievement_manager", CFUNCTYPE(POINTER(IDiscordAchievementManager), POINTER(IDiscordCore))), 486 | ] 487 | 488 | class DiscordCreateParams(Structure): 489 | _fields_ = [ 490 | ("client_id", DiscordClientId), 491 | ("flags", c_uint64), 492 | ("events", POINTER(IDiscordCoreEvents)), 493 | ("event_data", c_void_p), 494 | ("application_events", POINTER(IDiscordApplicationEvents)), 495 | ("application_version", DiscordVersion), 496 | ("user_events", POINTER(IDiscordUserEvents)), 497 | ("user_version", DiscordVersion), 498 | ("image_events", POINTER(IDiscordImageEvents)), 499 | ("image_version", DiscordVersion), 500 | ("activity_events", POINTER(IDiscordActivityEvents)), 501 | ("activity_version", DiscordVersion), 502 | ("relationship_events", POINTER(IDiscordRelationshipEvents)), 503 | ("relationship_version", DiscordVersion), 504 | ("lobby_events", POINTER(IDiscordLobbyEvents)), 505 | ("lobby_version", DiscordVersion), 506 | ("network_events", POINTER(IDiscordNetworkEvents)), 507 | ("network_version", DiscordVersion), 508 | ("overlay_events", POINTER(IDiscordOverlayEvents)), 509 | ("overlay_version", DiscordVersion), 510 | ("storage_events", POINTER(IDiscordStorageEvents)), 511 | ("storage_version", DiscordVersion), 512 | ("store_events", POINTER(IDiscordStoreEvents)), 513 | ("store_version", DiscordVersion), 514 | ("voice_events", POINTER(IDiscordVoiceEvents)), 515 | ("voice_version", DiscordVersion), 516 | ("achievement_events", POINTER(IDiscordAchievementEvents)), 517 | ("achievement_version", DiscordVersion) 518 | ] 519 | 520 | def DiscordCreateParamsSetDefault(params): 521 | params.application_version = 1 522 | params.user_version = 1 523 | params.image_version = 1 524 | params.activity_version = 1 525 | params.relationship_version = 1 526 | params.lobby_version = 1 527 | params.network_version = 1 528 | params.overlay_version = 2 529 | params.storage_version = 1 530 | params.store_version = 1 531 | params.voice_version = 1 532 | params.achievement_version = 1 533 | 534 | 535 | 536 | dll = CDLL(os.path.abspath("discord_game_sdk")) 537 | DiscordCreate = dll.DiscordCreate 538 | DiscordCreate.argtypes = (DiscordVersion, POINTER(DiscordCreateParams), POINTER(POINTER(IDiscordCore))) 539 | DiscordCreate.restype = c_int32 540 | 541 | -------------------------------------------------------------------------------- /discord/storage.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .enum import Result 3 | from .exception import getException 4 | from .model import FileStat 5 | from typing import Callable, Optional 6 | import ctypes 7 | 8 | class StorageManager: 9 | def __init__(self): 10 | self._internal = None 11 | self._garbage = [] 12 | self._events = None 13 | 14 | def GetPath(self) -> str: 15 | """ 16 | Returns the filepath to which Discord saves files if you were to use the SDK's storage manager. 17 | """ 18 | path = sdk.DiscordPath() 19 | 20 | result = Result(self._internal.get_path(self._internal, path)) 21 | if result != Result.Ok: 22 | raise getException(result) 23 | 24 | return path.value.decode("utf8") 25 | 26 | def Read(self, name: str) -> bytes: 27 | """ 28 | Reads data synchronously from the game's allocated save file. 29 | """ 30 | # we need the file stat for this one, as length-fixed buffers does not exist in python 31 | fileStat = self.Stat(name) 32 | fileSize = fileStat.Size 33 | 34 | name = ctypes.c_char_p(name.encode("utf8")) 35 | buffer = (ctypes.c_uint8 * fileSize)() 36 | read = ctypes.c_uint32() 37 | 38 | result = Result(self._internal.read(self._internal, name, buffer, len(buffer), read)) 39 | if result != Result.Ok: 40 | raise getException(result) 41 | 42 | if read.value != fileSize: 43 | print("discord/storage.py: warning: attempting to read " + str(fileSize) + " bytes, but read " + str(read.value)) 44 | 45 | return bytes(buffer[:read.value]) 46 | 47 | def ReadAsync(self, name: str, callback: Callable[[Result, Optional[bytes]], None]) -> None: 48 | """ 49 | Reads data asynchronously from the game's allocated save file. 50 | 51 | Returns discord.enum.Result (int) and data (bytes) via callback. 52 | """ 53 | def CCallback(callback_data, result, data, data_length): 54 | self._garbage.remove(CCallback) 55 | result = Result(result) 56 | if result == Result.Ok: 57 | data = bytes(data[:data_length]) 58 | callback(result, data) 59 | else: 60 | callback(result, None) 61 | 62 | CCallback = self._internal.read_async.argtypes[-1](CCallback) 63 | self._garbage.append(CCallback) # prevent it from being garbage collected 64 | 65 | name = ctypes.c_char_p(name.encode("utf8")) 66 | self._internal.read_async(self._internal, name, ctypes.c_void_p(), CCallback) 67 | 68 | def ReadAsyncPartial(self, name: str, offset: int, length: int, callback: Callable[[Result], None]) -> None: 69 | """ 70 | Reads data asynchronously from the game's allocated save file, starting at a given offset and up to a given length. 71 | """ 72 | def CCallback(callback_data, result, data, data_length): 73 | self._garbage.remove(CCallback) 74 | result = Result(result) 75 | if result == Result.Ok: 76 | data = bytes(data[:data_length]) 77 | callback(result, data) 78 | else: 79 | callback(result, None) 80 | 81 | CCallback = self._internal.read_async.argtypes[-1](CCallback) 82 | self._garbage.append(CCallback) # prevent it from being garbage collected 83 | 84 | name = ctypes.c_char_p(name.encode("utf8")) 85 | self._internal.read_async_partial(self._internal, name, offset, length, ctypes.c_void_p(), CCallback) 86 | 87 | def Write(self, name: str, data: bytes) -> None: 88 | """ 89 | Writes data synchronously to disk, under the given key name. 90 | """ 91 | name = ctypes.c_char_p(name.encode("utf8")) 92 | data = (ctypes.c_uint8 * len(data))(*data) 93 | 94 | result = Result(self._internal.write(self._internal, name, data, len(data))) 95 | if result != Result.Ok: 96 | raise getException(result) 97 | 98 | def WriteAsync(self, name: str, data: bytes, callback: Callable[[Result], None]) -> None: 99 | """ 100 | Writes data asynchronously to disk under the given keyname. 101 | """ 102 | def CCallback(callback_data, result): 103 | self._garbage.remove(CCallback) 104 | result = Result(result) 105 | callback(result) 106 | 107 | CCallback = self._internal.write_async.argtypes[-1](CCallback) 108 | self._garbage.append(CCallback) # prevent it from being garbage collected 109 | 110 | name = ctypes.c_char_p(name.encode("utf8")) 111 | data = (ctypes.c_uint8 * len(data))(*data) 112 | 113 | self._internal.write_async(self._internal, name, data, len(data), ctypes.c_void_p(), CCallback) 114 | 115 | def Delete(self, name: str) -> None: 116 | """ 117 | Deletes written data for the given key name. 118 | """ 119 | name = ctypes.c_char_p(name.encode("utf8")) 120 | 121 | result = Result(self._internal.delete_(self._internal, name)) 122 | if result != Result.Ok: 123 | raise getException(result) 124 | 125 | def Exists(self, name: str) -> bool: 126 | """ 127 | Checks if data exists for a given key name. 128 | """ 129 | exists = ctypes.c_bool() 130 | name = ctypes.c_char_p(name.encode("utf8")) 131 | 132 | result = Result(self._internal.exists(self._internal, name, exists)) 133 | if result != Result.Ok: 134 | raise getException(result) 135 | 136 | return exists.value 137 | 138 | def Stat(self, name: str) -> FileStat: 139 | """ 140 | Returns file info for the given key name. 141 | """ 142 | stat = sdk.DiscordFileStat() 143 | 144 | name = ctypes.c_char_p(name.encode("utf8")) 145 | result = Result(self._internal.stat(self._internal, name, stat)) 146 | if result != Result.Ok: 147 | raise getException(result) 148 | 149 | return FileStat(internal = stat) 150 | 151 | def Count(self) -> int: 152 | """ 153 | Returns the count of files, for iteration. 154 | """ 155 | count = ctypes.c_int32() 156 | self._internal.count(self._internal, count) 157 | return count.value 158 | 159 | def StatAt(self, index: int) -> FileStat: 160 | """ 161 | Returns file info for the given index when iterating over files. 162 | """ 163 | stat = sdk.DiscordFileStat() 164 | 165 | result = Result(self._internal.stat_at(self._internal, index, stat)) 166 | if result != Result.Ok: 167 | raise getException(result) 168 | 169 | return FileStat(internal = stat) -------------------------------------------------------------------------------- /discord/store.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .enum import Result 3 | from .model import Sku, Entitlement 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable 7 | import ctypes 8 | 9 | class StoreManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordStoreEvents, 14 | self._OnEntitlementCreate, 15 | self._OnEntitlementDelete 16 | ) 17 | 18 | def _OnEntitlementCreate(self, event_data, entitlement): 19 | self.OnEntitlementCreate(Entitlement(copy = entitlement)) 20 | 21 | def _OnEntitlementDelete(self, event_data, entitlement): 22 | self.OnEntitlementDelete(Entitlement(copy = entitlement)) 23 | 24 | def FetchSkus(self, callback: Callable[[Result], None]) -> None: 25 | """ 26 | Fetches the list of SKUs for the connected application, readying them for iteration. 27 | 28 | Returns discord.enum.Result (int) via callback. 29 | """ 30 | def CCallback(callback_data, result): 31 | self._garbage.remove(CCallback) 32 | result = Result(result) 33 | callback(result) 34 | 35 | CCallback = self._internal.fetch_skus.argtypes[-1](CCallback) 36 | self._garbage.append(CCallback) # prevent it from being garbage collected 37 | 38 | self._internal.fetch_skus(self._internal, ctypes.c_void_p(), CCallback) 39 | 40 | def CountSkus(self) -> int: 41 | """ 42 | Get the number of SKUs readied by FetchSkus(). 43 | """ 44 | count = ctypes.c_int32() 45 | self._internal.count_skus(self._internal, count) 46 | return count.value 47 | 48 | def GetSku(self, skuId: int) -> Sku: 49 | """ 50 | Gets a SKU by its ID. 51 | """ 52 | sku = sdk.DiscordSku() 53 | 54 | result = Result(self._internal.get_sku(skuId, sku)) 55 | if result != Result.Ok: 56 | raise getException(result) 57 | 58 | return Sku(internal = sku) 59 | 60 | def GetSkuAt(self, index: int) -> Sku: 61 | """ 62 | Gets a SKU by index when iterating over SKUs. 63 | """ 64 | sku = sdk.DiscordSku() 65 | 66 | result = Result(self._internal.get_sku_at(index, sku)) 67 | if result != Result.Ok: 68 | raise getException(result) 69 | 70 | return Sku(internal = sku) 71 | 72 | def FetchEntitlements(self, callback: Callable[[Result], None]) -> None: 73 | """ 74 | Fetches a list of entitlements to which the user is entitled. 75 | 76 | Returns discord.enum.Result (int) via callback. 77 | """ 78 | def CCallback(callback_data, result): 79 | self._garbage.remove(CCallback) 80 | result = Result(result) 81 | callback(result) 82 | 83 | CCallback = self._internal.fetch_entitlements.argtypes[-1](CCallback) 84 | self._garbage.append(CCallback) # prevent it from being garbage collected 85 | 86 | self._internal.fetch_entitlements(self._internal, ctypes.c_void_p(), CCallback) 87 | 88 | def CountEntitlements(self) -> int: 89 | """ 90 | Get the number of entitlements readied by FetchEntitlements(). 91 | """ 92 | count = ctypes.c_int32() 93 | self._internal.count_entitlements(self._internal, count) 94 | return count.value 95 | 96 | def GetEntitlement(self, entitlementId: int) -> Entitlement: 97 | """ 98 | Gets an entitlement by its id. 99 | """ 100 | entitlement = sdk.DiscordEntitlement() 101 | 102 | result = Result(self._internal.get_entitlement(entitlementId, entitlement)) 103 | if result != Result.Ok: 104 | raise getException(result) 105 | 106 | return Entitlement(internal = sku) 107 | 108 | def GetEntitlementAt(self, index: int) -> Entitlement: 109 | """ 110 | Gets an entitlement by index when iterating over a user's entitlements. 111 | """ 112 | entitlement = sdk.DiscordEntitlement() 113 | 114 | result = Result(self._internal.get_entitlement_at(index, entitlement)) 115 | if result != Result.Ok: 116 | raise getException(result) 117 | 118 | return Entitlement(internal = sku) 119 | 120 | def HasSkuEntitlement(self, skuId: int) -> bool: 121 | """ 122 | Returns whether or not the user is entitled to the given SKU ID. 123 | """ 124 | has_entitlement = ctypes.c_bool() 125 | 126 | result = Result(self._internal.has_sku_entitlement(skuId, has_entitlement)) 127 | if result != Result.Ok: 128 | raise getException(result) 129 | 130 | return has_entitlement.value 131 | 132 | def StartPurchase(self, skuId: int, callback: Callable[[Result], None]) -> None: 133 | """ 134 | Opens the overlay to begin the in-app purchase dialogue for the given SKU ID. 135 | 136 | Returns discord.enum.Result (int) via callback. 137 | """ 138 | def CCallback(callback_data, result): 139 | self._garbage.remove(CCallback) 140 | result = Result(result) 141 | callback(result) 142 | 143 | CCallback = self._internal.start_purchase.argtypes[-1](CCallback) 144 | self._garbage.append(CCallback) # prevent it from being garbage collected 145 | 146 | self._internal.start_purchase(self._internal, skuId, ctypes.c_void_p(), CCallback) 147 | 148 | def OnEntitlementCreate(self, entitlement: Entitlement) -> None: 149 | """ 150 | Fires when the connected user receives a new entitlement, either through purchase or through a developer grant. 151 | """ 152 | pass 153 | 154 | def OnEntitlementDelete(self, entitlement: Entitlement) -> None: 155 | """ 156 | Fires when the connected user loses an entitlement, either by expiration, revocation, or consumption in the case of consumable entitlements. 157 | """ 158 | pass 159 | -------------------------------------------------------------------------------- /discord/user.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import User 3 | from .enum import Result, PremiumType, UserFlag 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable, Optional 7 | import ctypes 8 | 9 | class UserManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordUserEvents, 14 | self._OnCurrentUserUpdate 15 | ) 16 | 17 | def _OnCurrentUserUpdate(self, event_data): 18 | self.OnCurrentUserUpdate() 19 | 20 | def GetCurrentUser(self) -> User: 21 | """ 22 | Fetch information about the currently connected user account. 23 | """ 24 | user = sdk.DiscordUser() 25 | result = Result(self._internal.get_current_user(self._internal, user)) 26 | if result != Result.Ok: 27 | raise getException(result) 28 | 29 | return User(internal = user) 30 | 31 | def GetUser(self, userId: int, callback: Callable[[Result, Optional[User]], None]) -> None: 32 | """ 33 | Get user information for a given id. 34 | 35 | Returns discord.enum.Result (int) and User via callback. 36 | """ 37 | def CCallback(callback_data, result, user): 38 | self._garbage.remove(CCallback) 39 | result = Result(result) 40 | if result == Result.Ok: 41 | callback(result, User(copy = user.contents)) 42 | else: 43 | callback(result, None) 44 | 45 | CCallback = self._internal.get_user.argtypes[-1](CCallback) 46 | self._garbage.append(CCallback) # prevent it from being garbage collected 47 | 48 | self._internal.get_user(self._internal, userId, ctypes.c_void_p(), CCallback) 49 | 50 | def GetCurrentUserPremiumType(self) -> PremiumType: 51 | """ 52 | Get the PremiumType for the currently connected user. 53 | """ 54 | premiumType = ctypes.c_int32() 55 | result = Result(self._internal.get_current_user_premium_type(self._internal, premiumType)) 56 | if result != Result.Ok: 57 | raise getException(result) 58 | 59 | return PremiumType(premiumType.value) 60 | 61 | def CurrentUserHasFlag(self, flag: UserFlag) -> bool: 62 | """ 63 | See whether or not the current user has a certain UserFlag on their account. 64 | """ 65 | hasFlag = ctypes.c_bool() 66 | result = Result(self._internal.current_user_has_flag(self._internal, flag, hasFlag)) 67 | if result != Result.Ok: 68 | raise getException(result) 69 | 70 | return hasFlag.value 71 | 72 | def OnCurrentUserUpdate(self) -> None: 73 | """ 74 | Fires when the User struct of the currently connected user changes. 75 | """ 76 | pass 77 | -------------------------------------------------------------------------------- /discord/voice.py: -------------------------------------------------------------------------------- 1 | from . import sdk 2 | from .model import InputMode 3 | from .enum import Result 4 | from .event import bindEvents 5 | from .exception import getException 6 | from typing import Callable 7 | import ctypes 8 | 9 | class VoiceManager: 10 | def __init__(self): 11 | self._internal = None 12 | self._garbage = [] 13 | self._events = bindEvents(sdk.IDiscordVoiceEvents, 14 | self._OnSettingsUpdate 15 | ) 16 | 17 | def _OnSettingsUpdate(self, event_data): 18 | self.OnSettingsUpdate() 19 | 20 | def GetInputMode(self) -> InputMode: 21 | """ 22 | Get the current voice input mode for the user 23 | """ 24 | input_mode = sdk.DiscordInputMode() 25 | result = Result(self._internal.get_input_mode(self._internal, input_mode)) 26 | if result != Result.Ok: 27 | raise getException(result) 28 | 29 | return InputMode(internal = input_mode) 30 | 31 | def SetInputMode(self, inputMode: InputMode, callback: Callable[[Result], None]) -> None: 32 | """ 33 | Sets a new voice input mode for the uesr. 34 | 35 | Returns discord.enum.Result (int) via callback. 36 | """ 37 | def CCallback(callback_data, result): 38 | self._garbage.remove(CCallback) 39 | result = Result(result) 40 | callback(result) 41 | 42 | CCallback = self._internal.set_input_mode.argtypes[-1](CCallback) 43 | self._garbage.append(CCallback) # prevent it from being garbage collected 44 | 45 | self._internal.set_input_mode(self._internal, inputMode._internal, ctypes.c_void_p(), CCallback) 46 | 47 | def IsSelfMute(self) -> bool: 48 | """ 49 | Whether the connected user is currently muted. 50 | """ 51 | mute = ctypes.c_bool() 52 | result = Result(self._internal.is_self_mute(self._internal, mute)) 53 | if result != Result.Ok: 54 | raise getException(result) 55 | 56 | return mute.value 57 | 58 | def SetSelfMute(self, mute: bool) -> None: 59 | """ 60 | Mutes or unmutes the currently connected user. 61 | """ 62 | result = Result(self._internal.set_self_mute(self._internal, mute)) 63 | if result != Result.Ok: 64 | raise getException(result) 65 | 66 | def IsSelfDeaf(self) -> bool: 67 | """ 68 | Whether the connected user is currently deafened. 69 | """ 70 | deaf = ctypes.c_bool() 71 | result = Result(self._internal.is_self_deaf(self._internal, deaf)) 72 | if result != Result.Ok: 73 | raise getException(result) 74 | 75 | return deaf.value 76 | 77 | def SetSelfDeaf(self, deaf: bool) -> None: 78 | """ 79 | Deafens or undefeans the currently connected user. 80 | """ 81 | result = Result(self._internal.set_self_deaf(self._internal, deaf)) 82 | if result != Result.Ok: 83 | raise getException(result) 84 | 85 | def IsLocalMute(self, userId: int) -> bool: 86 | """ 87 | Whether the given user is currently muted by the connected user. 88 | """ 89 | mute = ctypes.c_bool() 90 | result = Result(self._internal.is_local_mute(self._internal, userId, mute)) 91 | if result != Result.Ok: 92 | raise getException(result) 93 | 94 | return mute.value 95 | 96 | def SetLocalMute(self, userId: int, mute: bool) -> None: 97 | """ 98 | Mutes or unmutes the given user for the currently connected user. 99 | """ 100 | result = Result(self._internal.set_local_mute(self._internal, userId, mute)) 101 | if result != Result.Ok: 102 | raise getException(result) 103 | 104 | def GetLocalVolume(self, userId: int) -> int: 105 | """ 106 | Gets the local volume for a given user. 107 | """ 108 | volume = ctypes.c_uint8() 109 | result = Result(self._internal.get_local_volume(self._internal, userId, volume)) 110 | if result != Result.Ok: 111 | raise getException(result) 112 | 113 | return volume.value 114 | 115 | def SetLocalVolume(self, userId: int, volume: int) -> None: 116 | """ 117 | Sets the local volume for a given user. 118 | """ 119 | result = Result(self._internal.set_local_volume(self._internal, userId, volume)) 120 | if result != Result.Ok: 121 | raise getException(result) 122 | 123 | def OnSettingsUpdate(self) -> None: 124 | # This event is not documented anywhere (yet?) 125 | pass -------------------------------------------------------------------------------- /examples/activity.py: -------------------------------------------------------------------------------- 1 | # discord is in the parent folder 2 | import sys 3 | sys.path.insert(0, "..") 4 | 5 | # we can now import it 6 | from discord import Discord 7 | from discord.enum import CreateFlags, Result, ActivityJoinRequestReply 8 | from discord.model import Activity 9 | import time, uuid 10 | 11 | # we get the application id from a file 12 | with open("application_id.txt", "r") as file: 13 | applicationId = int(file.read()) 14 | 15 | # debug callback 16 | def debugCallback(debug, result, *args): 17 | if result == Result.Ok: 18 | print(debug, "success") 19 | else: 20 | print(debug, "failure", result, args) 21 | 22 | # we create the discord instance 23 | app = Discord(applicationId, CreateFlags.Default) 24 | activityManager = app.GetActivityManager() 25 | 26 | # events 27 | def onActivityJoin(secret): 28 | print("[onActivityJoin]") 29 | print("Secret", secret) 30 | 31 | def onActivitySpectate(secret): 32 | print("[onActivitySpectate]") 33 | print("Secret", secret) 34 | 35 | def onActivityJoinRequest(user): 36 | print("[onActivityJoinRequest]") 37 | print("User", user.Username) 38 | 39 | activityManager.SendRequestReply(user.Id, ActivityJoinRequestReply.Yes, lambda result: debugCallback("SendRequestReply", result)) 40 | 41 | def onActivityInvite(type, user, activity): 42 | print("[onActivityInvite]") 43 | print("Type", type) 44 | print("User", user.Username) 45 | print("Activity", activity.State) 46 | 47 | activityManager.AcceptInvite(user.Id, lambda result: debugCallback("AcceptInvite", result)) 48 | 49 | # bind events 50 | activityManager.OnActivityJoin = onActivityJoin 51 | activityManager.OnActivitySpectate = onActivitySpectate 52 | activityManager.OnActivityJoinRequest = onActivityJoinRequest 53 | activityManager.OnActivityInvite = onActivityInvite 54 | 55 | # we create an activity 56 | activity = Activity() 57 | activity.State = "Testing Game SDK" 58 | activity.Party.Id = str(uuid.uuid4()) 59 | activity.Party.Size.CurrentSize = 4 60 | activity.Party.Size.MaxSize = 8 61 | activity.Secrets.Join = str(uuid.uuid4()) 62 | 63 | # we update the activity 64 | activityManager.UpdateActivity(activity, lambda result: debugCallback("UpdateActivity", result)) 65 | 66 | # we set the command 67 | activityManager.RegisterCommand("iexplore.exe http://www.example.com/") 68 | 69 | timer = 0 70 | 71 | while 1: 72 | time.sleep(1/10) 73 | app.RunCallbacks() 74 | 75 | timer += 1 76 | if timer == 600: # clear activity after 60 seconds 77 | activityManager.ClearActivity(lambda result: debugCallback("ClearActivity", result)) -------------------------------------------------------------------------------- /examples/image.py: -------------------------------------------------------------------------------- 1 | # discord is in the parent folder 2 | import sys 3 | sys.path.insert(0, "..") 4 | 5 | # we can now import it 6 | from discord import Discord 7 | from discord.enum import CreateFlags, Result, ImageType 8 | from discord.model import ImageHandle 9 | from PIL import Image 10 | import time, uuid 11 | 12 | # we get the application id from a file 13 | with open("application_id.txt", "r") as file: 14 | applicationId = int(file.read()) 15 | 16 | # we create the discord instance 17 | app = Discord(applicationId, CreateFlags.Default) 18 | userManager = app.GetUserManager() 19 | imageManager = app.GetImageManager() 20 | 21 | # callbacks 22 | def onImageLoaded(result, handle): 23 | if result != Result.Ok: 24 | print("failed to fetch the image (result " + str(result) + ")") 25 | else: 26 | print("fetched the image!") 27 | print("handle:", handle.Type, handle.Id, handle.Size) 28 | 29 | dimensions = imageManager.GetDimensions(handle) 30 | print("dimensions:", dimensions.Width, dimensions.Height) 31 | 32 | # we load the image 33 | data = imageManager.GetData(handle) 34 | im = Image.frombytes("RGBA", (dimensions.Width, dimensions.Height), data) 35 | im.show() 36 | 37 | # events 38 | def onCurrentUserUpdate(): 39 | user = userManager.GetCurrentUser() 40 | print(f"hello, {user.Username}#{user.Discriminator}!") 41 | 42 | # we create an handle 43 | handle = ImageHandle() 44 | handle.Type = ImageType.User 45 | handle.Id = user.Id 46 | handle.Size = 256 47 | 48 | # we fetch the image 49 | imageManager.Fetch(handle, True, onImageLoaded) 50 | 51 | # bind events 52 | userManager.OnCurrentUserUpdate = onCurrentUserUpdate 53 | 54 | while 1: 55 | time.sleep(1/10) 56 | app.RunCallbacks() 57 | -------------------------------------------------------------------------------- /examples/network.py: -------------------------------------------------------------------------------- 1 | # You need two discord instances running 2 | # This is probably more a test than an example 3 | 4 | # Discord is in the parent folder 5 | import sys 6 | sys.path.insert(0, "..") 7 | 8 | from discord import Discord 9 | from discord.enum import CreateFlags, Result, ActivityJoinRequestReply 10 | from discord.model import Activity 11 | import discord.exception 12 | 13 | import time, uuid, os 14 | 15 | class Game: 16 | # We will set it dynamically 17 | ApplicationId = None 18 | 19 | def __init__(self, instanceId): 20 | self.instanceId = instanceId 21 | os.environ["DISCORD_INSTANCE_ID"] = str(self.instanceId) 22 | 23 | self.discord = Discord(Game.ApplicationId, CreateFlags.Default) 24 | 25 | self.networkManager = self.discord.GetNetworkManager() 26 | self.networkManager.OnRouteUpdate = self.onRouteUpdate 27 | self.networkManager.OnMessage = self.onMessage 28 | 29 | self.peerId = self.networkManager.GetPeerId() 30 | self.route = None 31 | self.connected = False 32 | 33 | def onRouteUpdate(self, route): 34 | self.route = route 35 | print(f"[Discord {self.instanceId}] Route: {self.route}") 36 | 37 | self.onRoute() 38 | 39 | def onMessage(self, peerId, channelId, data): 40 | print(f"[Discord {self.instanceId}] Received from {peerId} on channel {channelId}: {repr(data)}") 41 | 42 | # We get the Application Id 43 | with open("application_id.txt", "r") as file: 44 | Game.ApplicationId = int(file.read()) 45 | 46 | game0 = Game(0) 47 | game1 = Game(1) 48 | 49 | def onGame0Route(): 50 | if not game1.connected: 51 | print(f"[Discord {game1.instanceId}] Connecting to other peer {game0.peerId} on route {game0.route}") 52 | game1.networkManager.OpenPeer(game0.peerId, game0.route) 53 | game1.networkManager.OpenChannel(game0.peerId, 0, True) # reliable channel 54 | game1.networkManager.OpenChannel(game0.peerId, 1, False) # unreliable channel 55 | game1.connected = True 56 | else: 57 | game1.networkManager.UpdatePeer(game1.peerId, game1.route) 58 | 59 | def onGame1Route(): 60 | if not game0.connected: 61 | print(f"[Discord {game0.instanceId}] Connecting to other peer {game1.peerId} on route {game1.route}") 62 | game0.networkManager.OpenPeer(game1.peerId, game1.route) 63 | game0.networkManager.OpenChannel(game1.peerId, 0, True) # reliable channel 64 | game0.networkManager.OpenChannel(game1.peerId, 1, False) # unreliable channel 65 | game0.connected = True 66 | 67 | else: 68 | game0.networkManager.UpdatePeer(game0.peerId, game0.route) 69 | 70 | game0.onRoute = onGame0Route 71 | game1.onRoute = onGame1Route 72 | 73 | count = 0 74 | nextPacket = 0 75 | 76 | while 1: 77 | time.sleep(1/30) 78 | game0.discord.RunCallbacks() 79 | game1.discord.RunCallbacks() 80 | 81 | game0.networkManager.Flush() 82 | game1.networkManager.Flush() 83 | 84 | if game0.connected and game1.connected and time.time() > nextPacket: 85 | if count == 30: 86 | print(f"[Discord {game1.instanceId}] Closing connection to peer {game0.peerId}") 87 | game1.networkManager.ClosePeer(game0.peerId) 88 | 89 | # We stop after 40 (*2) sent packets. 90 | if count == 40: 91 | break 92 | 93 | else: 94 | game0.networkManager.SendMessage(game1.peerId, 0, ("reliable " + str(count)).encode("ascii")) 95 | print(f"[Discord {game0.instanceId}] Sent a packet to {game1.peerId} on channel 0") 96 | game0.networkManager.SendMessage(game1.peerId, 1, ("not reliable " + str(count)).encode("ascii")) 97 | print(f"[Discord {game0.instanceId}] Sent a packet to {game1.peerId} on channel 1") 98 | count += 1 99 | 100 | nextPacket = time.time() + 0.5 101 | -------------------------------------------------------------------------------- /examples/relationship.py: -------------------------------------------------------------------------------- 1 | # discord is in the parent folder 2 | import sys 3 | sys.path.insert(0, "..") 4 | 5 | # we can now import it 6 | from discord import Discord 7 | from discord.enum import CreateFlags, Result, Status, RelationshipType 8 | from discord.model import Activity 9 | import time, random 10 | 11 | # we get the application id from a file 12 | with open("application_id.txt", "r") as file: 13 | applicationId = int(file.read()) 14 | 15 | # we create the discord instance 16 | app = Discord(applicationId, CreateFlags.Default) 17 | relationshipManager = app.GetRelationshipManager() 18 | 19 | # events 20 | def onRefresh(): 21 | print("[onRefresh]") 22 | 23 | # we filter friends 24 | relationshipManager.Filter(lambda relationship: relationship.Type == RelationshipType.Friend) 25 | 26 | # we get how many friends we have!! 27 | friendCount = relationshipManager.Count() 28 | friends = [] 29 | print("you have " + str(friendCount) + " friends!") 30 | 31 | for n in range(friendCount): 32 | # we get the friend at index n 33 | friend = relationshipManager.GetAt(n) 34 | 35 | # we add it to the list 36 | friends.append(friend) 37 | 38 | # we show it 39 | print(f"{friend.User.Username}#{friend.User.Discriminator}") 40 | 41 | if len(friends): 42 | print() 43 | 44 | # we get the friend with a random friend, by his ID 45 | randomFriend = random.choice(friends) 46 | print("fetching " + str(randomFriend.User.Id)) 47 | 48 | friend = relationshipManager.Get(randomFriend.User.Id) 49 | print("we found %s" % friend.User.Username) 50 | 51 | # let's get implicit relationships 52 | relationshipManager.Filter(lambda relationship: relationship.Type == RelationshipType.Implicit) 53 | 54 | print() 55 | print("implicit relationships:") 56 | for n in range(relationshipManager.Count()): 57 | relationship = relationshipManager.GetAt(n) 58 | print(f" {relationship.User.Username}#{relationship.User.Discriminator}") 59 | 60 | def onRelationshipUpdate(relationship): 61 | print("[onRelationshipUpdate]") 62 | print("relationship", relationship.User.Username) 63 | 64 | # bind events 65 | relationshipManager.OnRefresh = onRefresh 66 | relationshipManager.OnRelationshipUpdate = onRelationshipUpdate 67 | 68 | while 1: 69 | time.sleep(1/10) 70 | app.RunCallbacks() 71 | -------------------------------------------------------------------------------- /examples/user.py: -------------------------------------------------------------------------------- 1 | # discord is in the parent folder 2 | import sys 3 | sys.path.insert(0, "..") 4 | 5 | # we can now import it 6 | from discord import Discord 7 | from discord.enum import CreateFlags, Result, PremiumType, UserFlag 8 | import time, uuid 9 | 10 | # we get the application id from a file 11 | with open("application_id.txt", "r") as file: 12 | applicationId = int(file.read()) 13 | 14 | # we create the discord instance 15 | app = Discord(applicationId, CreateFlags.Default) 16 | userManager = app.GetUserManager() 17 | 18 | # events 19 | def onCurrentUserUpdate(): 20 | print("[onCurrentUserUpdate]") 21 | user = userManager.GetCurrentUser() 22 | print(f"hello, {user.Username}#{user.Discriminator}!") 23 | 24 | premiumType = userManager.GetCurrentUserPremiumType() 25 | if premiumType == PremiumType.None_: 26 | print("you are not a nitro subscriber :(") 27 | elif premiumType == PremiumType.Tier1: 28 | print("you are a nitro classic subscriber!") 29 | elif premiumType == PremiumType.Tier2: 30 | print("you are a nitro subscriber!") 31 | 32 | if userManager.CurrentUserHasFlag(UserFlag.HypeSquadHouse1): 33 | print("you are a member of house bravery") 34 | if userManager.CurrentUserHasFlag(UserFlag.HypeSquadHouse2): 35 | print("you are a member of house brillance") 36 | if userManager.CurrentUserHasFlag(UserFlag.HypeSquadHouse3): 37 | print("you are a member of house balance") 38 | 39 | # bind events 40 | userManager.OnCurrentUserUpdate = onCurrentUserUpdate 41 | 42 | def callback(result, user): 43 | if result != Result.Ok: 44 | print("we failed to get user (result " + str(result) + ")") 45 | else: 46 | print("we have found the user! " + str(user.Username) + "#" + str(user.Discriminator)) 47 | 48 | # we search for the owner of the repo 49 | userManager.GetUser(336834315130503169, callback) 50 | 51 | while 1: 52 | time.sleep(1/10) 53 | app.RunCallbacks() --------------------------------------------------------------------------------