├── Host ├── YTDPwin │ ├── YTDPsetup │ │ └── desktop.ini │ ├── YTDPwin │ │ ├── YTDPwin.vcxproj.user │ │ ├── YTDPwin.vcxproj.filters │ │ └── YTDPwin.vcxproj │ └── YTDPwin.sln ├── YTDPlogo.ico ├── Discord │ ├── Lib │ │ ├── discord_game_sdk.dll │ │ └── discord_game_sdk.dll.lib │ └── API │ │ ├── discord.h │ │ ├── image_manager.h │ │ ├── user_manager.h │ │ ├── application_manager.h │ │ ├── relationship_manager.h │ │ ├── overlay_manager.h │ │ ├── voice_manager.h │ │ ├── achievement_manager.h │ │ ├── store_manager.h │ │ ├── event.h │ │ ├── activity_manager.h │ │ ├── storage_manager.h │ │ ├── network_manager.h │ │ ├── image_manager.cpp │ │ ├── core.h │ │ ├── user_manager.cpp │ │ ├── relationship_manager.cpp │ │ ├── application_manager.cpp │ │ ├── network_manager.cpp │ │ ├── achievement_manager.cpp │ │ ├── voice_manager.cpp │ │ ├── overlay_manager.cpp │ │ ├── lobby_manager.h │ │ ├── store_manager.cpp │ │ ├── core.cpp │ │ ├── storage_manager.cpp │ │ ├── activity_manager.cpp │ │ ├── types.h │ │ └── types.cpp ├── main.json └── main.cpp ├── Extension ├── Images │ ├── icon16.png │ ├── icon48.png │ ├── icon128.png │ └── ytdpSettings.png ├── manifest.json ├── content_loader.js ├── content.js ├── popup.css ├── popup.html └── background.js ├── Screenshots ├── newUiExample.png ├── ytdpScreenshot1.png ├── ytdpScreenshot2.png ├── ytdpScreenshot3.png └── ytdpScreenshot4.png ├── NodeHost ├── package.json ├── src │ └── app.js └── package-lock.json ├── .gitignore ├── LICENSE.txt └── README.md /Host/YTDPwin/YTDPsetup/desktop.ini: -------------------------------------------------------------------------------- 1 | [ViewState] 2 | Mode= 3 | Vid= 4 | FolderType=Generic 5 | -------------------------------------------------------------------------------- /Host/YTDPlogo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Host/YTDPlogo.ico -------------------------------------------------------------------------------- /Extension/Images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Extension/Images/icon16.png -------------------------------------------------------------------------------- /Extension/Images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Extension/Images/icon48.png -------------------------------------------------------------------------------- /Extension/Images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Extension/Images/icon128.png -------------------------------------------------------------------------------- /Screenshots/newUiExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Screenshots/newUiExample.png -------------------------------------------------------------------------------- /Screenshots/ytdpScreenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Screenshots/ytdpScreenshot1.png -------------------------------------------------------------------------------- /Screenshots/ytdpScreenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Screenshots/ytdpScreenshot2.png -------------------------------------------------------------------------------- /Screenshots/ytdpScreenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Screenshots/ytdpScreenshot3.png -------------------------------------------------------------------------------- /Screenshots/ytdpScreenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Screenshots/ytdpScreenshot4.png -------------------------------------------------------------------------------- /Extension/Images/ytdpSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Extension/Images/ytdpSettings.png -------------------------------------------------------------------------------- /Host/Discord/Lib/discord_game_sdk.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Host/Discord/Lib/discord_game_sdk.dll -------------------------------------------------------------------------------- /Host/Discord/Lib/discord_game_sdk.dll.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFG16/YouTubeDiscordPresence/HEAD/Host/Discord/Lib/discord_game_sdk.dll.lib -------------------------------------------------------------------------------- /Host/YTDPwin/YTDPwin/YTDPwin.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Host/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.ytdp.discord.presence", 3 | "description": "Component of the YouTubeDiscordPresence extension that allows the usage of native messaging.", 4 | "path": "YTDPwin.exe", 5 | "type": "stdio", 6 | "allowed_origins": [ 7 | "chrome-extension://hnmeidgkfcbpjjjpmjmpehjdljlaeaaa/" 8 | ] 9 | } -------------------------------------------------------------------------------- /NodeHost/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-discord-presence", 3 | "version": "1.0.0", 4 | "description": "NodeJS version of the YouTubeDiscordPresence desktop component", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "node ./src/app.js" 8 | }, 9 | "author": "Michael Ren", 10 | "license": "MIT", 11 | "dependencies": { 12 | "discord-rpc": "^4.0.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Host/Discord/API/discord.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | #include "core.h" 5 | #include "application_manager.h" 6 | #include "user_manager.h" 7 | #include "image_manager.h" 8 | #include "activity_manager.h" 9 | #include "relationship_manager.h" 10 | #include "lobby_manager.h" 11 | #include "network_manager.h" 12 | #include "overlay_manager.h" 13 | #include "storage_manager.h" 14 | #include "store_manager.h" 15 | #include "voice_manager.h" 16 | #include "achievement_manager.h" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Host/main.dSYM 2 | Host/log.txt 3 | Extension.zip 4 | Extension/.DS_Store 5 | .DS_Store 6 | Host/YTDPwin/x64 7 | Host/YTDPwin/YTDPwin/log.txt 8 | Host/YTDPwin/YTDPwin/x64 9 | Host/YTDPwin/UpgradeLog.htm 10 | Host/YTDPwin/UpgradeLog2.htm 11 | Host/YTDPwin/YTDPsetup/Debug 12 | Host/YTDPwin/YTDPsetup/Release 13 | .vscode 14 | .vs 15 | *.o 16 | *.msi 17 | *.exe 18 | *.out 19 | *.zip 20 | NodeHost/node_modules 21 | NodeHost/src/app-linux 22 | NodeHost/src/app-macos 23 | NodeHost/src/bundle.js 24 | test.js 25 | config.json 26 | test.bat 27 | .env 28 | -------------------------------------------------------------------------------- /Host/Discord/API/image_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class ImageManager final { 8 | public: 9 | ~ImageManager() = default; 10 | 11 | void Fetch(ImageHandle handle, bool refresh, std::function callback); 12 | Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions); 13 | Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength); 14 | 15 | private: 16 | friend class Core; 17 | 18 | ImageManager() = default; 19 | ImageManager(ImageManager const& rhs) = delete; 20 | ImageManager& operator=(ImageManager const& rhs) = delete; 21 | ImageManager(ImageManager&& rhs) = delete; 22 | ImageManager& operator=(ImageManager&& rhs) = delete; 23 | 24 | IDiscordImageManager* internal_; 25 | static IDiscordImageEvents events_; 26 | }; 27 | 28 | } // namespace discord 29 | -------------------------------------------------------------------------------- /Host/Discord/API/user_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class UserManager final { 8 | public: 9 | ~UserManager() = default; 10 | 11 | Result GetCurrentUser(User* currentUser); 12 | void GetUser(UserId userId, std::function callback); 13 | Result GetCurrentUserPremiumType(PremiumType* premiumType); 14 | Result CurrentUserHasFlag(UserFlag flag, bool* hasFlag); 15 | 16 | Event<> OnCurrentUserUpdate; 17 | 18 | private: 19 | friend class Core; 20 | 21 | UserManager() = default; 22 | UserManager(UserManager const& rhs) = delete; 23 | UserManager& operator=(UserManager const& rhs) = delete; 24 | UserManager(UserManager&& rhs) = delete; 25 | UserManager& operator=(UserManager&& rhs) = delete; 26 | 27 | IDiscordUserManager* internal_; 28 | static IDiscordUserEvents events_; 29 | }; 30 | 31 | } // namespace discord 32 | -------------------------------------------------------------------------------- /Host/Discord/API/application_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class ApplicationManager final { 8 | public: 9 | ~ApplicationManager() = default; 10 | 11 | void ValidateOrExit(std::function callback); 12 | void GetCurrentLocale(char locale[128]); 13 | void GetCurrentBranch(char branch[4096]); 14 | void GetOAuth2Token(std::function callback); 15 | void GetTicket(std::function callback); 16 | 17 | private: 18 | friend class Core; 19 | 20 | ApplicationManager() = default; 21 | ApplicationManager(ApplicationManager const& rhs) = delete; 22 | ApplicationManager& operator=(ApplicationManager const& rhs) = delete; 23 | ApplicationManager(ApplicationManager&& rhs) = delete; 24 | ApplicationManager& operator=(ApplicationManager&& rhs) = delete; 25 | 26 | IDiscordApplicationManager* internal_; 27 | static IDiscordApplicationEvents events_; 28 | }; 29 | 30 | } // namespace discord 31 | -------------------------------------------------------------------------------- /Host/Discord/API/relationship_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class RelationshipManager final { 8 | public: 9 | ~RelationshipManager() = default; 10 | 11 | void Filter(std::function filter); 12 | Result Count(std::int32_t* count); 13 | Result Get(UserId userId, Relationship* relationship); 14 | Result GetAt(std::uint32_t index, Relationship* relationship); 15 | 16 | Event<> OnRefresh; 17 | Event OnRelationshipUpdate; 18 | 19 | private: 20 | friend class Core; 21 | 22 | RelationshipManager() = default; 23 | RelationshipManager(RelationshipManager const& rhs) = delete; 24 | RelationshipManager& operator=(RelationshipManager const& rhs) = delete; 25 | RelationshipManager(RelationshipManager&& rhs) = delete; 26 | RelationshipManager& operator=(RelationshipManager&& rhs) = delete; 27 | 28 | IDiscordRelationshipManager* internal_; 29 | static IDiscordRelationshipEvents events_; 30 | }; 31 | 32 | } // namespace discord 33 | -------------------------------------------------------------------------------- /Host/Discord/API/overlay_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class OverlayManager final { 8 | public: 9 | ~OverlayManager() = default; 10 | 11 | void IsEnabled(bool* enabled); 12 | void IsLocked(bool* locked); 13 | void SetLocked(bool locked, std::function callback); 14 | void OpenActivityInvite(ActivityActionType type, std::function callback); 15 | void OpenGuildInvite(char const* code, std::function callback); 16 | void OpenVoiceSettings(std::function callback); 17 | 18 | Event OnToggle; 19 | 20 | private: 21 | friend class Core; 22 | 23 | OverlayManager() = default; 24 | OverlayManager(OverlayManager const& rhs) = delete; 25 | OverlayManager& operator=(OverlayManager const& rhs) = delete; 26 | OverlayManager(OverlayManager&& rhs) = delete; 27 | OverlayManager& operator=(OverlayManager&& rhs) = delete; 28 | 29 | IDiscordOverlayManager* internal_; 30 | static IDiscordOverlayEvents events_; 31 | }; 32 | 33 | } // namespace discord 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022–Present Michael Ren 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Host/Discord/API/voice_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class VoiceManager final { 8 | public: 9 | ~VoiceManager() = default; 10 | 11 | Result GetInputMode(InputMode* inputMode); 12 | void SetInputMode(InputMode inputMode, std::function callback); 13 | Result IsSelfMute(bool* mute); 14 | Result SetSelfMute(bool mute); 15 | Result IsSelfDeaf(bool* deaf); 16 | Result SetSelfDeaf(bool deaf); 17 | Result IsLocalMute(Snowflake userId, bool* mute); 18 | Result SetLocalMute(Snowflake userId, bool mute); 19 | Result GetLocalVolume(Snowflake userId, std::uint8_t* volume); 20 | Result SetLocalVolume(Snowflake userId, std::uint8_t volume); 21 | 22 | Event<> OnSettingsUpdate; 23 | 24 | private: 25 | friend class Core; 26 | 27 | VoiceManager() = default; 28 | VoiceManager(VoiceManager const& rhs) = delete; 29 | VoiceManager& operator=(VoiceManager const& rhs) = delete; 30 | VoiceManager(VoiceManager&& rhs) = delete; 31 | VoiceManager& operator=(VoiceManager&& rhs) = delete; 32 | 33 | IDiscordVoiceManager* internal_; 34 | static IDiscordVoiceEvents events_; 35 | }; 36 | 37 | } // namespace discord 38 | -------------------------------------------------------------------------------- /Host/Discord/API/achievement_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class AchievementManager final { 8 | public: 9 | ~AchievementManager() = default; 10 | 11 | void SetUserAchievement(Snowflake achievementId, 12 | std::uint8_t percentComplete, 13 | std::function callback); 14 | void FetchUserAchievements(std::function callback); 15 | void CountUserAchievements(std::int32_t* count); 16 | Result GetUserAchievement(Snowflake userAchievementId, UserAchievement* userAchievement); 17 | Result GetUserAchievementAt(std::int32_t index, UserAchievement* userAchievement); 18 | 19 | Event OnUserAchievementUpdate; 20 | 21 | private: 22 | friend class Core; 23 | 24 | AchievementManager() = default; 25 | AchievementManager(AchievementManager const& rhs) = delete; 26 | AchievementManager& operator=(AchievementManager const& rhs) = delete; 27 | AchievementManager(AchievementManager&& rhs) = delete; 28 | AchievementManager& operator=(AchievementManager&& rhs) = delete; 29 | 30 | IDiscordAchievementManager* internal_; 31 | static IDiscordAchievementEvents events_; 32 | }; 33 | 34 | } // namespace discord 35 | -------------------------------------------------------------------------------- /Extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTubeDiscordPresence", 3 | "author": "Michael Ren", 4 | "description": "An extension used to create a detailed rich presence for YouTube videos on Discord.", 5 | "version": "1.5.8", 6 | "manifest_version": 3, 7 | "content_scripts": [ 8 | { 9 | "matches": [ 10 | "https://www.youtube.com/*", 11 | "https://music.youtube.com/*" 12 | ], 13 | "js": [ 14 | "content_loader.js" 15 | ] 16 | } 17 | ], 18 | "background": { 19 | "service_worker": "background.js" 20 | }, 21 | "action": { 22 | "default_icon": "/Images/icon16.png", 23 | "default_popup": "popup.html" 24 | }, 25 | "web_accessible_resources": [ 26 | { 27 | "resources": [ 28 | "content.js", 29 | "warning.html" 30 | ], 31 | "matches": [ 32 | "https://www.youtube.com/*", 33 | "https://music.youtube.com/*" 34 | ] 35 | } 36 | ], 37 | "permissions": [ 38 | "nativeMessaging", 39 | "background", 40 | "storage", 41 | "tabs" 42 | ], 43 | "icons": { 44 | "16": "/Images/icon16.png", 45 | "48": "/Images/icon48.png", 46 | "128": "/Images/icon128.png" 47 | } 48 | } -------------------------------------------------------------------------------- /Host/Discord/API/store_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class StoreManager final { 8 | public: 9 | ~StoreManager() = default; 10 | 11 | void FetchSkus(std::function callback); 12 | void CountSkus(std::int32_t* count); 13 | Result GetSku(Snowflake skuId, Sku* sku); 14 | Result GetSkuAt(std::int32_t index, Sku* sku); 15 | void FetchEntitlements(std::function callback); 16 | void CountEntitlements(std::int32_t* count); 17 | Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement); 18 | Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement); 19 | Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement); 20 | void StartPurchase(Snowflake skuId, std::function callback); 21 | 22 | Event OnEntitlementCreate; 23 | Event OnEntitlementDelete; 24 | 25 | private: 26 | friend class Core; 27 | 28 | StoreManager() = default; 29 | StoreManager(StoreManager const& rhs) = delete; 30 | StoreManager& operator=(StoreManager const& rhs) = delete; 31 | StoreManager(StoreManager&& rhs) = delete; 32 | StoreManager& operator=(StoreManager&& rhs) = delete; 33 | 34 | IDiscordStoreManager* internal_; 35 | static IDiscordStoreEvents events_; 36 | }; 37 | 38 | } // namespace discord 39 | -------------------------------------------------------------------------------- /Host/Discord/API/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace discord { 7 | 8 | template 9 | class Event final { 10 | public: 11 | using Token = int; 12 | 13 | Event() { slots_.reserve(4); } 14 | 15 | Event(Event const&) = default; 16 | Event(Event&&) = default; 17 | ~Event() = default; 18 | 19 | Event& operator=(Event const&) = default; 20 | Event& operator=(Event&&) = default; 21 | 22 | template 23 | Token Connect(EventHandler slot) 24 | { 25 | slots_.emplace_back(Slot{nextToken_, std::move(slot)}); 26 | return nextToken_++; 27 | } 28 | 29 | void Disconnect(Token token) 30 | { 31 | for (auto& slot : slots_) { 32 | if (slot.token == token) { 33 | slot = slots_.back(); 34 | slots_.pop_back(); 35 | break; 36 | } 37 | } 38 | } 39 | 40 | void DisconnectAll() { slots_ = {}; } 41 | 42 | void operator()(Args... args) 43 | { 44 | for (auto const& slot : slots_) { 45 | slot.fn(std::forward(args)...); 46 | } 47 | } 48 | 49 | private: 50 | struct Slot { 51 | Token token; 52 | std::function fn; 53 | }; 54 | 55 | Token nextToken_{}; 56 | std::vector slots_{}; 57 | }; 58 | 59 | } // namespace discord 60 | -------------------------------------------------------------------------------- /Host/Discord/API/activity_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class ActivityManager final { 8 | public: 9 | ~ActivityManager() = default; 10 | 11 | Result RegisterCommand(char const* command); 12 | Result RegisterSteam(std::uint32_t steamId); 13 | void UpdateActivity(Activity const& activity, std::function callback); 14 | void ClearActivity(std::function callback); 15 | void SendRequestReply(UserId userId, 16 | ActivityJoinRequestReply reply, 17 | std::function callback); 18 | void SendInvite(UserId userId, 19 | ActivityActionType type, 20 | char const* content, 21 | std::function callback); 22 | void AcceptInvite(UserId userId, std::function callback); 23 | 24 | Event OnActivityJoin; 25 | Event OnActivitySpectate; 26 | Event OnActivityJoinRequest; 27 | Event OnActivityInvite; 28 | 29 | private: 30 | friend class Core; 31 | 32 | ActivityManager() = default; 33 | ActivityManager(ActivityManager const& rhs) = delete; 34 | ActivityManager& operator=(ActivityManager const& rhs) = delete; 35 | ActivityManager(ActivityManager&& rhs) = delete; 36 | ActivityManager& operator=(ActivityManager&& rhs) = delete; 37 | 38 | IDiscordActivityManager* internal_; 39 | static IDiscordActivityEvents events_; 40 | }; 41 | 42 | } // namespace discord 43 | -------------------------------------------------------------------------------- /Host/Discord/API/storage_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class StorageManager final { 8 | public: 9 | ~StorageManager() = default; 10 | 11 | Result Read(char const* name, 12 | std::uint8_t* data, 13 | std::uint32_t dataLength, 14 | std::uint32_t* read); 15 | void ReadAsync(char const* name, 16 | std::function callback); 17 | void ReadAsyncPartial(char const* name, 18 | std::uint64_t offset, 19 | std::uint64_t length, 20 | std::function callback); 21 | Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength); 22 | void WriteAsync(char const* name, 23 | std::uint8_t* data, 24 | std::uint32_t dataLength, 25 | std::function callback); 26 | Result Delete(char const* name); 27 | Result Exists(char const* name, bool* exists); 28 | void Count(std::int32_t* count); 29 | Result Stat(char const* name, FileStat* stat); 30 | Result StatAt(std::int32_t index, FileStat* stat); 31 | Result GetPath(char path[4096]); 32 | 33 | private: 34 | friend class Core; 35 | 36 | StorageManager() = default; 37 | StorageManager(StorageManager const& rhs) = delete; 38 | StorageManager& operator=(StorageManager const& rhs) = delete; 39 | StorageManager(StorageManager&& rhs) = delete; 40 | StorageManager& operator=(StorageManager&& rhs) = delete; 41 | 42 | IDiscordStorageManager* internal_; 43 | static IDiscordStorageEvents events_; 44 | }; 45 | 46 | } // namespace discord 47 | -------------------------------------------------------------------------------- /Host/Discord/API/network_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class NetworkManager final { 8 | public: 9 | ~NetworkManager() = default; 10 | 11 | /** 12 | * Get the local peer ID for this process. 13 | */ 14 | void GetPeerId(NetworkPeerId* peerId); 15 | /** 16 | * Send pending network messages. 17 | */ 18 | Result Flush(); 19 | /** 20 | * Open a connection to a remote peer. 21 | */ 22 | Result OpenPeer(NetworkPeerId peerId, char const* routeData); 23 | /** 24 | * Update the route data for a connected peer. 25 | */ 26 | Result UpdatePeer(NetworkPeerId peerId, char const* routeData); 27 | /** 28 | * Close the connection to a remote peer. 29 | */ 30 | Result ClosePeer(NetworkPeerId peerId); 31 | /** 32 | * Open a message channel to a connected peer. 33 | */ 34 | Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable); 35 | /** 36 | * Close a message channel to a connected peer. 37 | */ 38 | Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId); 39 | /** 40 | * Send a message to a connected peer over an opened message channel. 41 | */ 42 | Result SendMessage(NetworkPeerId peerId, 43 | NetworkChannelId channelId, 44 | std::uint8_t* data, 45 | std::uint32_t dataLength); 46 | 47 | Event OnMessage; 48 | Event OnRouteUpdate; 49 | 50 | private: 51 | friend class Core; 52 | 53 | NetworkManager() = default; 54 | NetworkManager(NetworkManager const& rhs) = delete; 55 | NetworkManager& operator=(NetworkManager const& rhs) = delete; 56 | NetworkManager(NetworkManager&& rhs) = delete; 57 | NetworkManager& operator=(NetworkManager&& rhs) = delete; 58 | 59 | IDiscordNetworkManager* internal_; 60 | static IDiscordNetworkEvents events_; 61 | }; 62 | 63 | } // namespace discord 64 | -------------------------------------------------------------------------------- /Host/YTDPwin/YTDPwin.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32519.379 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "YTDPwin", "YTDPwin\YTDPwin.vcxproj", "{FE7AD499-27B7-4135-AF92-20E7E3984FC3}" 7 | EndProject 8 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "YTDPsetup", "YTDPsetup\YTDPsetup.vdproj", "{22527C5D-CC35-471C-8505-B7688C5E8490}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Debug|x64.ActiveCfg = Debug|x64 19 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Debug|x64.Build.0 = Debug|x64 20 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Debug|x86.ActiveCfg = Debug|Win32 21 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Debug|x86.Build.0 = Debug|Win32 22 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Release|x64.ActiveCfg = Release|x64 23 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Release|x64.Build.0 = Release|x64 24 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Release|x86.ActiveCfg = Release|Win32 25 | {FE7AD499-27B7-4135-AF92-20E7E3984FC3}.Release|x86.Build.0 = Release|Win32 26 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Debug|x64.ActiveCfg = Debug 27 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Debug|x64.Build.0 = Debug 28 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Debug|x86.ActiveCfg = Debug 29 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Release|x64.ActiveCfg = Release 30 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Release|x64.Build.0 = Release 31 | {22527C5D-CC35-471C-8505-B7688C5E8490}.Release|x86.ActiveCfg = Release 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {21E7BB46-2E06-4678-BF07-032F67BEFAC7} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /Extension/content_loader.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022–Present Michael Ren 3 | Licensing and distribution info can be found at the GitHub repository 4 | https://github.com/XFG16/YouTubeDiscordPresence 5 | */ 6 | 7 | // MAIN VARIABLE INITIALIZATION 8 | 9 | const LOGGING = false; 10 | 11 | const UPDATE_PRESENCE_MESSAGE = "UPDATE_PRESENCE_DATA"; 12 | 13 | // REDIRECTION OF DATA FROM INJECTED CONTENT.JS TO BACKGROUND.JS 14 | 15 | window.addEventListener("SendToLoader", function (message) { 16 | chrome.runtime.sendMessage({ 17 | messageType: UPDATE_PRESENCE_MESSAGE, 18 | title: message.detail.title, 19 | author: message.detail.author, 20 | timeLeft: message.detail.timeLeft, 21 | duration: message.detail.duration, 22 | videoId: message.detail.videoId, 23 | channelUrl: message.detail.channelUrl, 24 | applicationType: message.detail.applicationType, 25 | thumbnailUrl: message.detail.thumbnailUrl, 26 | }, (response) => { 27 | if (LOGGING) { 28 | console.log(`Data was sent by content_loader.js and received by background.js: ${message.detail}`); 29 | } 30 | }); 31 | }, false); 32 | 33 | // INJECTION OF CONTENT.JS INTO MAIN DOM 34 | 35 | var mainScript = document.createElement("script"); 36 | mainScript.src = chrome.runtime.getURL("/content.js"); 37 | (document.head || document.documentElement).appendChild(mainScript); 38 | mainScript.onload = function () { 39 | this.remove(); 40 | }; 41 | 42 | // NATIVE APP NOT CONNECTED WARNING 43 | 44 | // chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 45 | // if (message.removeWarning) { 46 | // let warning = document.getElementById("ytdpClientErrorContainer"); 47 | // if (warning) { 48 | // warning.remove(); 49 | // } 50 | // } 51 | // sendResponse(null); 52 | // }); 53 | 54 | // chrome.storage.sync.get("isNativeConnected", function (result) { 55 | // if (result.isNativeConnected == false) { 56 | // fetch(chrome.runtime.getURL('/warning.html')).then(r => r.text()).then(html => { 57 | // document.body.insertAdjacentHTML('beforeend', html); 58 | // }); 59 | // } 60 | // }); -------------------------------------------------------------------------------- /Host/Discord/API/image_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "image_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | void ImageManager::Fetch(ImageHandle handle, 15 | bool refresh, 16 | std::function callback) 17 | { 18 | static auto wrapper = 19 | [](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void { 20 | std::unique_ptr> cb( 21 | reinterpret_cast*>(callbackData)); 22 | if (!cb || !(*cb)) { 23 | return; 24 | } 25 | (*cb)(static_cast(result), *reinterpret_cast(&handleResult)); 26 | }; 27 | std::unique_ptr> cb{}; 28 | cb.reset(new std::function(std::move(callback))); 29 | internal_->fetch(internal_, 30 | *reinterpret_cast(&handle), 31 | (refresh ? 1 : 0), 32 | cb.release(), 33 | wrapper); 34 | } 35 | 36 | Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions) 37 | { 38 | if (!dimensions) { 39 | return Result::InternalError; 40 | } 41 | 42 | auto result = internal_->get_dimensions(internal_, 43 | *reinterpret_cast(&handle), 44 | reinterpret_cast(dimensions)); 45 | return static_cast(result); 46 | } 47 | 48 | Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength) 49 | { 50 | auto result = internal_->get_data(internal_, 51 | *reinterpret_cast(&handle), 52 | reinterpret_cast(data), 53 | dataLength); 54 | return static_cast(result); 55 | } 56 | 57 | } // namespace discord 58 | -------------------------------------------------------------------------------- /Host/Discord/API/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | #include "application_manager.h" 5 | #include "user_manager.h" 6 | #include "image_manager.h" 7 | #include "activity_manager.h" 8 | #include "relationship_manager.h" 9 | #include "lobby_manager.h" 10 | #include "network_manager.h" 11 | #include "overlay_manager.h" 12 | #include "storage_manager.h" 13 | #include "store_manager.h" 14 | #include "voice_manager.h" 15 | #include "achievement_manager.h" 16 | 17 | namespace discord { 18 | 19 | class Core final { 20 | public: 21 | static Result Create(ClientId clientId, std::uint64_t flags, Core** instance); 22 | 23 | ~Core(); 24 | 25 | Result RunCallbacks(); 26 | void SetLogHook(LogLevel minLevel, std::function hook); 27 | 28 | discord::ApplicationManager& ApplicationManager(); 29 | discord::UserManager& UserManager(); 30 | discord::ImageManager& ImageManager(); 31 | discord::ActivityManager& ActivityManager(); 32 | discord::RelationshipManager& RelationshipManager(); 33 | discord::LobbyManager& LobbyManager(); 34 | discord::NetworkManager& NetworkManager(); 35 | discord::OverlayManager& OverlayManager(); 36 | discord::StorageManager& StorageManager(); 37 | discord::StoreManager& StoreManager(); 38 | discord::VoiceManager& VoiceManager(); 39 | discord::AchievementManager& AchievementManager(); 40 | 41 | private: 42 | Core() = default; 43 | Core(Core const& rhs) = delete; 44 | Core& operator=(Core const& rhs) = delete; 45 | Core(Core&& rhs) = delete; 46 | Core& operator=(Core&& rhs) = delete; 47 | 48 | IDiscordCore* internal_; 49 | Event setLogHook_; 50 | discord::ApplicationManager applicationManager_; 51 | discord::UserManager userManager_; 52 | discord::ImageManager imageManager_; 53 | discord::ActivityManager activityManager_; 54 | discord::RelationshipManager relationshipManager_; 55 | discord::LobbyManager lobbyManager_; 56 | discord::NetworkManager networkManager_; 57 | discord::OverlayManager overlayManager_; 58 | discord::StorageManager storageManager_; 59 | discord::StoreManager storeManager_; 60 | discord::VoiceManager voiceManager_; 61 | discord::AchievementManager achievementManager_; 62 | }; 63 | 64 | } // namespace discord 65 | -------------------------------------------------------------------------------- /Host/Discord/API/user_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "user_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class UserEvents final { 15 | public: 16 | static void OnCurrentUserUpdate(void* callbackData) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->UserManager(); 24 | module.OnCurrentUserUpdate(); 25 | } 26 | }; 27 | 28 | IDiscordUserEvents UserManager::events_{ 29 | &UserEvents::OnCurrentUserUpdate, 30 | }; 31 | 32 | Result UserManager::GetCurrentUser(User* currentUser) 33 | { 34 | if (!currentUser) { 35 | return Result::InternalError; 36 | } 37 | 38 | auto result = 39 | internal_->get_current_user(internal_, reinterpret_cast(currentUser)); 40 | return static_cast(result); 41 | } 42 | 43 | void UserManager::GetUser(UserId userId, std::function callback) 44 | { 45 | static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void { 46 | std::unique_ptr> cb( 47 | reinterpret_cast*>(callbackData)); 48 | if (!cb || !(*cb)) { 49 | return; 50 | } 51 | (*cb)(static_cast(result), *reinterpret_cast(user)); 52 | }; 53 | std::unique_ptr> cb{}; 54 | cb.reset(new std::function(std::move(callback))); 55 | internal_->get_user(internal_, userId, cb.release(), wrapper); 56 | } 57 | 58 | Result UserManager::GetCurrentUserPremiumType(PremiumType* premiumType) 59 | { 60 | if (!premiumType) { 61 | return Result::InternalError; 62 | } 63 | 64 | auto result = internal_->get_current_user_premium_type( 65 | internal_, reinterpret_cast(premiumType)); 66 | return static_cast(result); 67 | } 68 | 69 | Result UserManager::CurrentUserHasFlag(UserFlag flag, bool* hasFlag) 70 | { 71 | if (!hasFlag) { 72 | return Result::InternalError; 73 | } 74 | 75 | auto result = internal_->current_user_has_flag( 76 | internal_, static_cast(flag), reinterpret_cast(hasFlag)); 77 | return static_cast(result); 78 | } 79 | 80 | } // namespace discord 81 | -------------------------------------------------------------------------------- /Host/Discord/API/relationship_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "relationship_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class RelationshipEvents final { 15 | public: 16 | static void OnRefresh(void* callbackData) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->RelationshipManager(); 24 | module.OnRefresh(); 25 | } 26 | 27 | static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship) 28 | { 29 | auto* core = reinterpret_cast(callbackData); 30 | if (!core) { 31 | return; 32 | } 33 | 34 | auto& module = core->RelationshipManager(); 35 | module.OnRelationshipUpdate(*reinterpret_cast(relationship)); 36 | } 37 | }; 38 | 39 | IDiscordRelationshipEvents RelationshipManager::events_{ 40 | &RelationshipEvents::OnRefresh, 41 | &RelationshipEvents::OnRelationshipUpdate, 42 | }; 43 | 44 | void RelationshipManager::Filter(std::function filter) 45 | { 46 | static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool { 47 | auto cb(reinterpret_cast*>(callbackData)); 48 | if (!cb || !(*cb)) { 49 | return {}; 50 | } 51 | return (*cb)(*reinterpret_cast(relationship)); 52 | }; 53 | std::unique_ptr> cb{}; 54 | cb.reset(new std::function(std::move(filter))); 55 | internal_->filter(internal_, cb.get(), wrapper); 56 | } 57 | 58 | Result RelationshipManager::Count(std::int32_t* count) 59 | { 60 | if (!count) { 61 | return Result::InternalError; 62 | } 63 | 64 | auto result = internal_->count(internal_, reinterpret_cast(count)); 65 | return static_cast(result); 66 | } 67 | 68 | Result RelationshipManager::Get(UserId userId, Relationship* relationship) 69 | { 70 | if (!relationship) { 71 | return Result::InternalError; 72 | } 73 | 74 | auto result = 75 | internal_->get(internal_, userId, reinterpret_cast(relationship)); 76 | return static_cast(result); 77 | } 78 | 79 | Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship) 80 | { 81 | if (!relationship) { 82 | return Result::InternalError; 83 | } 84 | 85 | auto result = 86 | internal_->get_at(internal_, index, reinterpret_cast(relationship)); 87 | return static_cast(result); 88 | } 89 | 90 | } // namespace discord 91 | -------------------------------------------------------------------------------- /Host/Discord/API/application_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "application_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | void ApplicationManager::ValidateOrExit(std::function callback) 15 | { 16 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 17 | std::unique_ptr> cb( 18 | reinterpret_cast*>(callbackData)); 19 | if (!cb || !(*cb)) { 20 | return; 21 | } 22 | (*cb)(static_cast(result)); 23 | }; 24 | std::unique_ptr> cb{}; 25 | cb.reset(new std::function(std::move(callback))); 26 | internal_->validate_or_exit(internal_, cb.release(), wrapper); 27 | } 28 | 29 | void ApplicationManager::GetCurrentLocale(char locale[128]) 30 | { 31 | if (!locale) { 32 | return; 33 | } 34 | 35 | internal_->get_current_locale(internal_, reinterpret_cast(locale)); 36 | } 37 | 38 | void ApplicationManager::GetCurrentBranch(char branch[4096]) 39 | { 40 | if (!branch) { 41 | return; 42 | } 43 | 44 | internal_->get_current_branch(internal_, reinterpret_cast(branch)); 45 | } 46 | 47 | void ApplicationManager::GetOAuth2Token(std::function callback) 48 | { 49 | static auto wrapper = 50 | [](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void { 51 | std::unique_ptr> cb( 52 | reinterpret_cast*>(callbackData)); 53 | if (!cb || !(*cb)) { 54 | return; 55 | } 56 | (*cb)(static_cast(result), *reinterpret_cast(oauth2Token)); 57 | }; 58 | std::unique_ptr> cb{}; 59 | cb.reset(new std::function(std::move(callback))); 60 | internal_->get_oauth2_token(internal_, cb.release(), wrapper); 61 | } 62 | 63 | void ApplicationManager::GetTicket(std::function callback) 64 | { 65 | static auto wrapper = [](void* callbackData, EDiscordResult result, char const* data) -> void { 66 | std::unique_ptr> cb( 67 | reinterpret_cast*>(callbackData)); 68 | if (!cb || !(*cb)) { 69 | return; 70 | } 71 | (*cb)(static_cast(result), static_cast(data)); 72 | }; 73 | std::unique_ptr> cb{}; 74 | cb.reset(new std::function(std::move(callback))); 75 | internal_->get_ticket(internal_, cb.release(), wrapper); 76 | } 77 | 78 | } // namespace discord 79 | -------------------------------------------------------------------------------- /Host/Discord/API/network_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "network_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class NetworkEvents final { 15 | public: 16 | static void OnMessage(void* callbackData, 17 | DiscordNetworkPeerId peerId, 18 | DiscordNetworkChannelId channelId, 19 | uint8_t* data, 20 | uint32_t dataLength) 21 | { 22 | auto* core = reinterpret_cast(callbackData); 23 | if (!core) { 24 | return; 25 | } 26 | 27 | auto& module = core->NetworkManager(); 28 | module.OnMessage(peerId, channelId, data, dataLength); 29 | } 30 | 31 | static void OnRouteUpdate(void* callbackData, char const* routeData) 32 | { 33 | auto* core = reinterpret_cast(callbackData); 34 | if (!core) { 35 | return; 36 | } 37 | 38 | auto& module = core->NetworkManager(); 39 | module.OnRouteUpdate(static_cast(routeData)); 40 | } 41 | }; 42 | 43 | IDiscordNetworkEvents NetworkManager::events_{ 44 | &NetworkEvents::OnMessage, 45 | &NetworkEvents::OnRouteUpdate, 46 | }; 47 | 48 | void NetworkManager::GetPeerId(NetworkPeerId* peerId) 49 | { 50 | if (!peerId) { 51 | return; 52 | } 53 | 54 | internal_->get_peer_id(internal_, reinterpret_cast(peerId)); 55 | } 56 | 57 | Result NetworkManager::Flush() 58 | { 59 | auto result = internal_->flush(internal_); 60 | return static_cast(result); 61 | } 62 | 63 | Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData) 64 | { 65 | auto result = internal_->open_peer(internal_, peerId, const_cast(routeData)); 66 | return static_cast(result); 67 | } 68 | 69 | Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData) 70 | { 71 | auto result = internal_->update_peer(internal_, peerId, const_cast(routeData)); 72 | return static_cast(result); 73 | } 74 | 75 | Result NetworkManager::ClosePeer(NetworkPeerId peerId) 76 | { 77 | auto result = internal_->close_peer(internal_, peerId); 78 | return static_cast(result); 79 | } 80 | 81 | Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable) 82 | { 83 | auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0)); 84 | return static_cast(result); 85 | } 86 | 87 | Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId) 88 | { 89 | auto result = internal_->close_channel(internal_, peerId, channelId); 90 | return static_cast(result); 91 | } 92 | 93 | Result NetworkManager::SendMessage(NetworkPeerId peerId, 94 | NetworkChannelId channelId, 95 | std::uint8_t* data, 96 | std::uint32_t dataLength) 97 | { 98 | auto result = internal_->send_message( 99 | internal_, peerId, channelId, reinterpret_cast(data), dataLength); 100 | return static_cast(result); 101 | } 102 | 103 | } // namespace discord 104 | -------------------------------------------------------------------------------- /Host/Discord/API/achievement_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "achievement_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class AchievementEvents final { 15 | public: 16 | static void OnUserAchievementUpdate(void* callbackData, DiscordUserAchievement* userAchievement) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->AchievementManager(); 24 | module.OnUserAchievementUpdate(*reinterpret_cast(userAchievement)); 25 | } 26 | }; 27 | 28 | IDiscordAchievementEvents AchievementManager::events_{ 29 | &AchievementEvents::OnUserAchievementUpdate, 30 | }; 31 | 32 | void AchievementManager::SetUserAchievement(Snowflake achievementId, 33 | std::uint8_t percentComplete, 34 | std::function callback) 35 | { 36 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 37 | std::unique_ptr> cb( 38 | reinterpret_cast*>(callbackData)); 39 | if (!cb || !(*cb)) { 40 | return; 41 | } 42 | (*cb)(static_cast(result)); 43 | }; 44 | std::unique_ptr> cb{}; 45 | cb.reset(new std::function(std::move(callback))); 46 | internal_->set_user_achievement( 47 | internal_, achievementId, percentComplete, cb.release(), wrapper); 48 | } 49 | 50 | void AchievementManager::FetchUserAchievements(std::function callback) 51 | { 52 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 53 | std::unique_ptr> cb( 54 | reinterpret_cast*>(callbackData)); 55 | if (!cb || !(*cb)) { 56 | return; 57 | } 58 | (*cb)(static_cast(result)); 59 | }; 60 | std::unique_ptr> cb{}; 61 | cb.reset(new std::function(std::move(callback))); 62 | internal_->fetch_user_achievements(internal_, cb.release(), wrapper); 63 | } 64 | 65 | void AchievementManager::CountUserAchievements(std::int32_t* count) 66 | { 67 | if (!count) { 68 | return; 69 | } 70 | 71 | internal_->count_user_achievements(internal_, reinterpret_cast(count)); 72 | } 73 | 74 | Result AchievementManager::GetUserAchievement(Snowflake userAchievementId, 75 | UserAchievement* userAchievement) 76 | { 77 | if (!userAchievement) { 78 | return Result::InternalError; 79 | } 80 | 81 | auto result = internal_->get_user_achievement( 82 | internal_, userAchievementId, reinterpret_cast(userAchievement)); 83 | return static_cast(result); 84 | } 85 | 86 | Result AchievementManager::GetUserAchievementAt(std::int32_t index, 87 | UserAchievement* userAchievement) 88 | { 89 | if (!userAchievement) { 90 | return Result::InternalError; 91 | } 92 | 93 | auto result = internal_->get_user_achievement_at( 94 | internal_, index, reinterpret_cast(userAchievement)); 95 | return static_cast(result); 96 | } 97 | 98 | } // namespace discord 99 | -------------------------------------------------------------------------------- /Host/Discord/API/voice_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "voice_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class VoiceEvents final { 15 | public: 16 | static void OnSettingsUpdate(void* callbackData) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->VoiceManager(); 24 | module.OnSettingsUpdate(); 25 | } 26 | }; 27 | 28 | IDiscordVoiceEvents VoiceManager::events_{ 29 | &VoiceEvents::OnSettingsUpdate, 30 | }; 31 | 32 | Result VoiceManager::GetInputMode(InputMode* inputMode) 33 | { 34 | if (!inputMode) { 35 | return Result::InternalError; 36 | } 37 | 38 | auto result = 39 | internal_->get_input_mode(internal_, reinterpret_cast(inputMode)); 40 | return static_cast(result); 41 | } 42 | 43 | void VoiceManager::SetInputMode(InputMode inputMode, std::function callback) 44 | { 45 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 46 | std::unique_ptr> cb( 47 | reinterpret_cast*>(callbackData)); 48 | if (!cb || !(*cb)) { 49 | return; 50 | } 51 | (*cb)(static_cast(result)); 52 | }; 53 | std::unique_ptr> cb{}; 54 | cb.reset(new std::function(std::move(callback))); 55 | internal_->set_input_mode( 56 | internal_, *reinterpret_cast(&inputMode), cb.release(), wrapper); 57 | } 58 | 59 | Result VoiceManager::IsSelfMute(bool* mute) 60 | { 61 | if (!mute) { 62 | return Result::InternalError; 63 | } 64 | 65 | auto result = internal_->is_self_mute(internal_, reinterpret_cast(mute)); 66 | return static_cast(result); 67 | } 68 | 69 | Result VoiceManager::SetSelfMute(bool mute) 70 | { 71 | auto result = internal_->set_self_mute(internal_, (mute ? 1 : 0)); 72 | return static_cast(result); 73 | } 74 | 75 | Result VoiceManager::IsSelfDeaf(bool* deaf) 76 | { 77 | if (!deaf) { 78 | return Result::InternalError; 79 | } 80 | 81 | auto result = internal_->is_self_deaf(internal_, reinterpret_cast(deaf)); 82 | return static_cast(result); 83 | } 84 | 85 | Result VoiceManager::SetSelfDeaf(bool deaf) 86 | { 87 | auto result = internal_->set_self_deaf(internal_, (deaf ? 1 : 0)); 88 | return static_cast(result); 89 | } 90 | 91 | Result VoiceManager::IsLocalMute(Snowflake userId, bool* mute) 92 | { 93 | if (!mute) { 94 | return Result::InternalError; 95 | } 96 | 97 | auto result = internal_->is_local_mute(internal_, userId, reinterpret_cast(mute)); 98 | return static_cast(result); 99 | } 100 | 101 | Result VoiceManager::SetLocalMute(Snowflake userId, bool mute) 102 | { 103 | auto result = internal_->set_local_mute(internal_, userId, (mute ? 1 : 0)); 104 | return static_cast(result); 105 | } 106 | 107 | Result VoiceManager::GetLocalVolume(Snowflake userId, std::uint8_t* volume) 108 | { 109 | if (!volume) { 110 | return Result::InternalError; 111 | } 112 | 113 | auto result = 114 | internal_->get_local_volume(internal_, userId, reinterpret_cast(volume)); 115 | return static_cast(result); 116 | } 117 | 118 | Result VoiceManager::SetLocalVolume(Snowflake userId, std::uint8_t volume) 119 | { 120 | auto result = internal_->set_local_volume(internal_, userId, volume); 121 | return static_cast(result); 122 | } 123 | 124 | } // namespace discord 125 | -------------------------------------------------------------------------------- /Host/Discord/API/overlay_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "overlay_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class OverlayEvents final { 15 | public: 16 | static void OnToggle(void* callbackData, bool locked) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->OverlayManager(); 24 | module.OnToggle((locked != 0)); 25 | } 26 | }; 27 | 28 | IDiscordOverlayEvents OverlayManager::events_{ 29 | &OverlayEvents::OnToggle, 30 | }; 31 | 32 | void OverlayManager::IsEnabled(bool* enabled) 33 | { 34 | if (!enabled) { 35 | return; 36 | } 37 | 38 | internal_->is_enabled(internal_, reinterpret_cast(enabled)); 39 | } 40 | 41 | void OverlayManager::IsLocked(bool* locked) 42 | { 43 | if (!locked) { 44 | return; 45 | } 46 | 47 | internal_->is_locked(internal_, reinterpret_cast(locked)); 48 | } 49 | 50 | void OverlayManager::SetLocked(bool locked, std::function callback) 51 | { 52 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 53 | std::unique_ptr> cb( 54 | reinterpret_cast*>(callbackData)); 55 | if (!cb || !(*cb)) { 56 | return; 57 | } 58 | (*cb)(static_cast(result)); 59 | }; 60 | std::unique_ptr> cb{}; 61 | cb.reset(new std::function(std::move(callback))); 62 | internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper); 63 | } 64 | 65 | void OverlayManager::OpenActivityInvite(ActivityActionType type, 66 | std::function callback) 67 | { 68 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 69 | std::unique_ptr> cb( 70 | reinterpret_cast*>(callbackData)); 71 | if (!cb || !(*cb)) { 72 | return; 73 | } 74 | (*cb)(static_cast(result)); 75 | }; 76 | std::unique_ptr> cb{}; 77 | cb.reset(new std::function(std::move(callback))); 78 | internal_->open_activity_invite( 79 | internal_, static_cast(type), cb.release(), wrapper); 80 | } 81 | 82 | void OverlayManager::OpenGuildInvite(char const* code, std::function callback) 83 | { 84 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 85 | std::unique_ptr> cb( 86 | reinterpret_cast*>(callbackData)); 87 | if (!cb || !(*cb)) { 88 | return; 89 | } 90 | (*cb)(static_cast(result)); 91 | }; 92 | std::unique_ptr> cb{}; 93 | cb.reset(new std::function(std::move(callback))); 94 | internal_->open_guild_invite(internal_, const_cast(code), cb.release(), wrapper); 95 | } 96 | 97 | void OverlayManager::OpenVoiceSettings(std::function callback) 98 | { 99 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 100 | std::unique_ptr> cb( 101 | reinterpret_cast*>(callbackData)); 102 | if (!cb || !(*cb)) { 103 | return; 104 | } 105 | (*cb)(static_cast(result)); 106 | }; 107 | std::unique_ptr> cb{}; 108 | cb.reset(new std::function(std::move(callback))); 109 | internal_->open_voice_settings(internal_, cb.release(), wrapper); 110 | } 111 | 112 | } // namespace discord 113 | -------------------------------------------------------------------------------- /Host/Discord/API/lobby_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace discord { 6 | 7 | class LobbyManager final { 8 | public: 9 | ~LobbyManager() = default; 10 | 11 | Result GetLobbyCreateTransaction(LobbyTransaction* transaction); 12 | Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction); 13 | Result GetMemberUpdateTransaction(LobbyId lobbyId, 14 | UserId userId, 15 | LobbyMemberTransaction* transaction); 16 | void CreateLobby(LobbyTransaction const& transaction, 17 | std::function callback); 18 | void UpdateLobby(LobbyId lobbyId, 19 | LobbyTransaction const& transaction, 20 | std::function callback); 21 | void DeleteLobby(LobbyId lobbyId, std::function callback); 22 | void ConnectLobby(LobbyId lobbyId, 23 | LobbySecret secret, 24 | std::function callback); 25 | void ConnectLobbyWithActivitySecret(LobbySecret activitySecret, 26 | std::function callback); 27 | void DisconnectLobby(LobbyId lobbyId, std::function callback); 28 | Result GetLobby(LobbyId lobbyId, Lobby* lobby); 29 | Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]); 30 | Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]); 31 | Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]); 32 | Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count); 33 | Result MemberCount(LobbyId lobbyId, std::int32_t* count); 34 | Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId); 35 | Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user); 36 | Result GetMemberMetadataValue(LobbyId lobbyId, 37 | UserId userId, 38 | MetadataKey key, 39 | char value[4096]); 40 | Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]); 41 | Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count); 42 | void UpdateMember(LobbyId lobbyId, 43 | UserId userId, 44 | LobbyMemberTransaction const& transaction, 45 | std::function callback); 46 | void SendLobbyMessage(LobbyId lobbyId, 47 | std::uint8_t* data, 48 | std::uint32_t dataLength, 49 | std::function callback); 50 | Result GetSearchQuery(LobbySearchQuery* query); 51 | void Search(LobbySearchQuery const& query, std::function callback); 52 | void LobbyCount(std::int32_t* count); 53 | Result GetLobbyId(std::int32_t index, LobbyId* lobbyId); 54 | void ConnectVoice(LobbyId lobbyId, std::function callback); 55 | void DisconnectVoice(LobbyId lobbyId, std::function callback); 56 | Result ConnectNetwork(LobbyId lobbyId); 57 | Result DisconnectNetwork(LobbyId lobbyId); 58 | Result FlushNetwork(); 59 | Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable); 60 | Result SendNetworkMessage(LobbyId lobbyId, 61 | UserId userId, 62 | std::uint8_t channelId, 63 | std::uint8_t* data, 64 | std::uint32_t dataLength); 65 | 66 | Event OnLobbyUpdate; 67 | Event OnLobbyDelete; 68 | Event OnMemberConnect; 69 | Event OnMemberUpdate; 70 | Event OnMemberDisconnect; 71 | Event OnLobbyMessage; 72 | Event OnSpeaking; 73 | Event OnNetworkMessage; 74 | 75 | private: 76 | friend class Core; 77 | 78 | LobbyManager() = default; 79 | LobbyManager(LobbyManager const& rhs) = delete; 80 | LobbyManager& operator=(LobbyManager const& rhs) = delete; 81 | LobbyManager(LobbyManager&& rhs) = delete; 82 | LobbyManager& operator=(LobbyManager&& rhs) = delete; 83 | 84 | IDiscordLobbyManager* internal_; 85 | static IDiscordLobbyEvents events_; 86 | }; 87 | 88 | } // namespace discord 89 | -------------------------------------------------------------------------------- /Host/YTDPwin/YTDPwin/YTDPwin.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | 65 | 66 | Header Files 67 | 68 | 69 | Header Files 70 | 71 | 72 | Header Files 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | Header Files 91 | 92 | 93 | Header Files 94 | 95 | 96 | Header Files 97 | 98 | 99 | Header Files 100 | 101 | 102 | Header Files 103 | 104 | 105 | Header Files 106 | 107 | 108 | Header Files 109 | 110 | 111 | Header Files 112 | 113 | 114 | -------------------------------------------------------------------------------- /Host/Discord/API/store_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "store_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class StoreEvents final { 15 | public: 16 | static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->StoreManager(); 24 | module.OnEntitlementCreate(*reinterpret_cast(entitlement)); 25 | } 26 | 27 | static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement) 28 | { 29 | auto* core = reinterpret_cast(callbackData); 30 | if (!core) { 31 | return; 32 | } 33 | 34 | auto& module = core->StoreManager(); 35 | module.OnEntitlementDelete(*reinterpret_cast(entitlement)); 36 | } 37 | }; 38 | 39 | IDiscordStoreEvents StoreManager::events_{ 40 | &StoreEvents::OnEntitlementCreate, 41 | &StoreEvents::OnEntitlementDelete, 42 | }; 43 | 44 | void StoreManager::FetchSkus(std::function callback) 45 | { 46 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 47 | std::unique_ptr> cb( 48 | reinterpret_cast*>(callbackData)); 49 | if (!cb || !(*cb)) { 50 | return; 51 | } 52 | (*cb)(static_cast(result)); 53 | }; 54 | std::unique_ptr> cb{}; 55 | cb.reset(new std::function(std::move(callback))); 56 | internal_->fetch_skus(internal_, cb.release(), wrapper); 57 | } 58 | 59 | void StoreManager::CountSkus(std::int32_t* count) 60 | { 61 | if (!count) { 62 | return; 63 | } 64 | 65 | internal_->count_skus(internal_, reinterpret_cast(count)); 66 | } 67 | 68 | Result StoreManager::GetSku(Snowflake skuId, Sku* sku) 69 | { 70 | if (!sku) { 71 | return Result::InternalError; 72 | } 73 | 74 | auto result = internal_->get_sku(internal_, skuId, reinterpret_cast(sku)); 75 | return static_cast(result); 76 | } 77 | 78 | Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku) 79 | { 80 | if (!sku) { 81 | return Result::InternalError; 82 | } 83 | 84 | auto result = internal_->get_sku_at(internal_, index, reinterpret_cast(sku)); 85 | return static_cast(result); 86 | } 87 | 88 | void StoreManager::FetchEntitlements(std::function callback) 89 | { 90 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 91 | std::unique_ptr> cb( 92 | reinterpret_cast*>(callbackData)); 93 | if (!cb || !(*cb)) { 94 | return; 95 | } 96 | (*cb)(static_cast(result)); 97 | }; 98 | std::unique_ptr> cb{}; 99 | cb.reset(new std::function(std::move(callback))); 100 | internal_->fetch_entitlements(internal_, cb.release(), wrapper); 101 | } 102 | 103 | void StoreManager::CountEntitlements(std::int32_t* count) 104 | { 105 | if (!count) { 106 | return; 107 | } 108 | 109 | internal_->count_entitlements(internal_, reinterpret_cast(count)); 110 | } 111 | 112 | Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement) 113 | { 114 | if (!entitlement) { 115 | return Result::InternalError; 116 | } 117 | 118 | auto result = internal_->get_entitlement( 119 | internal_, entitlementId, reinterpret_cast(entitlement)); 120 | return static_cast(result); 121 | } 122 | 123 | Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement) 124 | { 125 | if (!entitlement) { 126 | return Result::InternalError; 127 | } 128 | 129 | auto result = internal_->get_entitlement_at( 130 | internal_, index, reinterpret_cast(entitlement)); 131 | return static_cast(result); 132 | } 133 | 134 | Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement) 135 | { 136 | if (!hasEntitlement) { 137 | return Result::InternalError; 138 | } 139 | 140 | auto result = 141 | internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast(hasEntitlement)); 142 | return static_cast(result); 143 | } 144 | 145 | void StoreManager::StartPurchase(Snowflake skuId, std::function callback) 146 | { 147 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 148 | std::unique_ptr> cb( 149 | reinterpret_cast*>(callbackData)); 150 | if (!cb || !(*cb)) { 151 | return; 152 | } 153 | (*cb)(static_cast(result)); 154 | }; 155 | std::unique_ptr> cb{}; 156 | cb.reset(new std::function(std::move(callback))); 157 | internal_->start_purchase(internal_, skuId, cb.release(), wrapper); 158 | } 159 | 160 | } // namespace discord 161 | -------------------------------------------------------------------------------- /Host/Discord/API/core.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "core.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace discord { 11 | 12 | Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance) 13 | { 14 | if (!instance) { 15 | return Result::InternalError; 16 | } 17 | 18 | (*instance) = new Core(); 19 | DiscordCreateParams params{}; 20 | DiscordCreateParamsSetDefault(¶ms); 21 | params.client_id = clientId; 22 | params.flags = flags; 23 | params.events = nullptr; 24 | params.event_data = *instance; 25 | params.user_events = &UserManager::events_; 26 | params.activity_events = &ActivityManager::events_; 27 | params.relationship_events = &RelationshipManager::events_; 28 | params.lobby_events = &LobbyManager::events_; 29 | params.network_events = &NetworkManager::events_; 30 | params.overlay_events = &OverlayManager::events_; 31 | params.store_events = &StoreManager::events_; 32 | params.voice_events = &VoiceManager::events_; 33 | params.achievement_events = &AchievementManager::events_; 34 | auto result = DiscordCreate(DISCORD_VERSION, ¶ms, &((*instance)->internal_)); 35 | if (result != DiscordResult_Ok || !(*instance)->internal_) { 36 | delete (*instance); 37 | (*instance) = nullptr; 38 | } 39 | 40 | return static_cast(result); 41 | } 42 | 43 | Core::~Core() 44 | { 45 | if (internal_) { 46 | internal_->destroy(internal_); 47 | internal_ = nullptr; 48 | } 49 | } 50 | 51 | Result Core::RunCallbacks() 52 | { 53 | auto result = internal_->run_callbacks(internal_); 54 | return static_cast(result); 55 | } 56 | 57 | void Core::SetLogHook(LogLevel minLevel, std::function hook) 58 | { 59 | setLogHook_.DisconnectAll(); 60 | setLogHook_.Connect(std::move(hook)); 61 | static auto wrapper = 62 | [](void* callbackData, EDiscordLogLevel level, char const* message) -> void { 63 | auto cb(reinterpret_cast(callbackData)); 64 | if (!cb) { 65 | return; 66 | } 67 | (*cb)(static_cast(level), static_cast(message)); 68 | }; 69 | 70 | internal_->set_log_hook( 71 | internal_, static_cast(minLevel), &setLogHook_, wrapper); 72 | } 73 | 74 | discord::ApplicationManager& Core::ApplicationManager() 75 | { 76 | if (!applicationManager_.internal_) { 77 | applicationManager_.internal_ = internal_->get_application_manager(internal_); 78 | } 79 | 80 | return applicationManager_; 81 | } 82 | 83 | discord::UserManager& Core::UserManager() 84 | { 85 | if (!userManager_.internal_) { 86 | userManager_.internal_ = internal_->get_user_manager(internal_); 87 | } 88 | 89 | return userManager_; 90 | } 91 | 92 | discord::ImageManager& Core::ImageManager() 93 | { 94 | if (!imageManager_.internal_) { 95 | imageManager_.internal_ = internal_->get_image_manager(internal_); 96 | } 97 | 98 | return imageManager_; 99 | } 100 | 101 | discord::ActivityManager& Core::ActivityManager() 102 | { 103 | if (!activityManager_.internal_) { 104 | activityManager_.internal_ = internal_->get_activity_manager(internal_); 105 | } 106 | 107 | return activityManager_; 108 | } 109 | 110 | discord::RelationshipManager& Core::RelationshipManager() 111 | { 112 | if (!relationshipManager_.internal_) { 113 | relationshipManager_.internal_ = internal_->get_relationship_manager(internal_); 114 | } 115 | 116 | return relationshipManager_; 117 | } 118 | 119 | discord::LobbyManager& Core::LobbyManager() 120 | { 121 | if (!lobbyManager_.internal_) { 122 | lobbyManager_.internal_ = internal_->get_lobby_manager(internal_); 123 | } 124 | 125 | return lobbyManager_; 126 | } 127 | 128 | discord::NetworkManager& Core::NetworkManager() 129 | { 130 | if (!networkManager_.internal_) { 131 | networkManager_.internal_ = internal_->get_network_manager(internal_); 132 | } 133 | 134 | return networkManager_; 135 | } 136 | 137 | discord::OverlayManager& Core::OverlayManager() 138 | { 139 | if (!overlayManager_.internal_) { 140 | overlayManager_.internal_ = internal_->get_overlay_manager(internal_); 141 | } 142 | 143 | return overlayManager_; 144 | } 145 | 146 | discord::StorageManager& Core::StorageManager() 147 | { 148 | if (!storageManager_.internal_) { 149 | storageManager_.internal_ = internal_->get_storage_manager(internal_); 150 | } 151 | 152 | return storageManager_; 153 | } 154 | 155 | discord::StoreManager& Core::StoreManager() 156 | { 157 | if (!storeManager_.internal_) { 158 | storeManager_.internal_ = internal_->get_store_manager(internal_); 159 | } 160 | 161 | return storeManager_; 162 | } 163 | 164 | discord::VoiceManager& Core::VoiceManager() 165 | { 166 | if (!voiceManager_.internal_) { 167 | voiceManager_.internal_ = internal_->get_voice_manager(internal_); 168 | } 169 | 170 | return voiceManager_; 171 | } 172 | 173 | discord::AchievementManager& Core::AchievementManager() 174 | { 175 | if (!achievementManager_.internal_) { 176 | achievementManager_.internal_ = internal_->get_achievement_manager(internal_); 177 | } 178 | 179 | return achievementManager_; 180 | } 181 | 182 | } // namespace discord 183 | -------------------------------------------------------------------------------- /Host/Discord/API/storage_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "storage_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | Result StorageManager::Read(char const* name, 15 | std::uint8_t* data, 16 | std::uint32_t dataLength, 17 | std::uint32_t* read) 18 | { 19 | if (!read) { 20 | return Result::InternalError; 21 | } 22 | 23 | auto result = internal_->read(internal_, 24 | const_cast(name), 25 | reinterpret_cast(data), 26 | dataLength, 27 | reinterpret_cast(read)); 28 | return static_cast(result); 29 | } 30 | 31 | void StorageManager::ReadAsync(char const* name, 32 | std::function callback) 33 | { 34 | static auto wrapper = 35 | [](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void { 36 | std::unique_ptr> cb( 37 | reinterpret_cast*>( 38 | callbackData)); 39 | if (!cb || !(*cb)) { 40 | return; 41 | } 42 | (*cb)(static_cast(result), data, dataLength); 43 | }; 44 | std::unique_ptr> cb{}; 45 | cb.reset(new std::function(std::move(callback))); 46 | internal_->read_async(internal_, const_cast(name), cb.release(), wrapper); 47 | } 48 | 49 | void StorageManager::ReadAsyncPartial( 50 | char const* name, 51 | std::uint64_t offset, 52 | std::uint64_t length, 53 | std::function callback) 54 | { 55 | static auto wrapper = 56 | [](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void { 57 | std::unique_ptr> cb( 58 | reinterpret_cast*>( 59 | callbackData)); 60 | if (!cb || !(*cb)) { 61 | return; 62 | } 63 | (*cb)(static_cast(result), data, dataLength); 64 | }; 65 | std::unique_ptr> cb{}; 66 | cb.reset(new std::function(std::move(callback))); 67 | internal_->read_async_partial( 68 | internal_, const_cast(name), offset, length, cb.release(), wrapper); 69 | } 70 | 71 | Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength) 72 | { 73 | auto result = internal_->write( 74 | internal_, const_cast(name), reinterpret_cast(data), dataLength); 75 | return static_cast(result); 76 | } 77 | 78 | void StorageManager::WriteAsync(char const* name, 79 | std::uint8_t* data, 80 | std::uint32_t dataLength, 81 | std::function callback) 82 | { 83 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 84 | std::unique_ptr> cb( 85 | reinterpret_cast*>(callbackData)); 86 | if (!cb || !(*cb)) { 87 | return; 88 | } 89 | (*cb)(static_cast(result)); 90 | }; 91 | std::unique_ptr> cb{}; 92 | cb.reset(new std::function(std::move(callback))); 93 | internal_->write_async(internal_, 94 | const_cast(name), 95 | reinterpret_cast(data), 96 | dataLength, 97 | cb.release(), 98 | wrapper); 99 | } 100 | 101 | Result StorageManager::Delete(char const* name) 102 | { 103 | auto result = internal_->delete_(internal_, const_cast(name)); 104 | return static_cast(result); 105 | } 106 | 107 | Result StorageManager::Exists(char const* name, bool* exists) 108 | { 109 | if (!exists) { 110 | return Result::InternalError; 111 | } 112 | 113 | auto result = 114 | internal_->exists(internal_, const_cast(name), reinterpret_cast(exists)); 115 | return static_cast(result); 116 | } 117 | 118 | void StorageManager::Count(std::int32_t* count) 119 | { 120 | if (!count) { 121 | return; 122 | } 123 | 124 | internal_->count(internal_, reinterpret_cast(count)); 125 | } 126 | 127 | Result StorageManager::Stat(char const* name, FileStat* stat) 128 | { 129 | if (!stat) { 130 | return Result::InternalError; 131 | } 132 | 133 | auto result = 134 | internal_->stat(internal_, const_cast(name), reinterpret_cast(stat)); 135 | return static_cast(result); 136 | } 137 | 138 | Result StorageManager::StatAt(std::int32_t index, FileStat* stat) 139 | { 140 | if (!stat) { 141 | return Result::InternalError; 142 | } 143 | 144 | auto result = internal_->stat_at(internal_, index, reinterpret_cast(stat)); 145 | return static_cast(result); 146 | } 147 | 148 | Result StorageManager::GetPath(char path[4096]) 149 | { 150 | if (!path) { 151 | return Result::InternalError; 152 | } 153 | 154 | auto result = internal_->get_path(internal_, reinterpret_cast(path)); 155 | return static_cast(result); 156 | } 157 | 158 | } // namespace discord 159 | -------------------------------------------------------------------------------- /Host/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Discord/API/discord.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | const int64_t APPLICATION_ID = 847682519214456862; 12 | const std::string TITLE_IDENTIFIER = ":TITLE001:"; 13 | const std::string AUTHOR_IDENTIFIER = ":AUTHOR002:"; 14 | const std::string TIME_LEFT_IDENTIFIER = ":TIMELEFT003:"; 15 | const std::string END_IDENTIFIER = ":END004:"; 16 | const std::string IDLE_IDENTIFIER = "#*IDLE*#"; 17 | const int LIVESTREAM_TIME_ID = -1; 18 | const int ACTIVITY_BUFFER_SIZE = 1024; 19 | 20 | const bool LOGGING = false; 21 | 22 | std::unique_ptr core; 23 | std::time_t elapsedTime = 0; 24 | 25 | std::string previousTitle; 26 | std::string previousAuthor; 27 | 28 | // CREATE A DISCORD PRESENCE IF ONE DOESN'T ALREADY EXIST 29 | 30 | bool createPresence(void) { 31 | if (core) { 32 | return true; 33 | } 34 | discord::Core* tempCore = nullptr; 35 | discord::Result result = discord::Core::Create(APPLICATION_ID, DiscordCreateFlags_NoRequireDiscord, &tempCore); 36 | if (LOGGING && result == discord::Result::Ok) { 37 | std::cout << "Discord presence has been created" << std::endl; 38 | } 39 | else if (LOGGING) { 40 | std::cout << "Failed to create Discord presence" << std::endl; 41 | } 42 | core.reset(tempCore); 43 | return result == discord::Result::Ok; 44 | } 45 | 46 | // DESTROY THE DISCORD PRESENCE IF IT EXISTS 47 | 48 | void destroyPresence(void) { 49 | if (!core) { 50 | return; 51 | } 52 | core.reset(nullptr); 53 | if (LOGGING && !core) { 54 | std::cout << "Discord presence has been destroyed" << std::endl; 55 | } 56 | else if (LOGGING) { 57 | std::cout << "Failed to destroy Discord presence" << std::endl; 58 | } 59 | } 60 | 61 | // FORMAT C STRINGS 62 | 63 | void formatCString(char* str) { // REMEMBER TO DEAL WITH OTHER SPECIAL CHARACTERS LATER 64 | int j = 0; 65 | for (int i = 0; str[i] != '\0'; ++i) { 66 | if (!(str[i] == '\\' && str[i + 1] == '\"') && !(str[i] == '\\' && str[i + 1] == '\'') && !(str[i] == '\\' && str[i + 1] == '\?') && !(str[i] == '\\' && str[i + 1] == '\\')) { 67 | str[j] = str[i]; 68 | ++j; 69 | } 70 | } 71 | memset(&str[j], '\0', ACTIVITY_BUFFER_SIZE - j); 72 | } 73 | 74 | // UPDATE DISCORD PRESENCE WITH DATA 75 | 76 | void updatePresence(const std::string& title, const std::string& author, const std::string& timeLeftStr) { 77 | if (core && (title == IDLE_IDENTIFIER || core->RunCallbacks() != discord::Result::Ok)) { 78 | destroyPresence(); 79 | return; 80 | } 81 | else if (!core) { 82 | if (title == IDLE_IDENTIFIER) { 83 | return; 84 | } 85 | bool didCreatePresence = createPresence(); 86 | if (!didCreatePresence) { 87 | destroyPresence(); 88 | return; 89 | } 90 | } 91 | int timeLeft = std::stoi(timeLeftStr); 92 | 93 | discord::Activity activity{}; 94 | discord::ActivityTimestamps& timeStamp = activity.GetTimestamps(); 95 | discord::ActivityAssets& activityAssets = activity.GetAssets(); 96 | 97 | char titleCString[ACTIVITY_BUFFER_SIZE], authorCString[ACTIVITY_BUFFER_SIZE]; 98 | memset(authorCString, '\0', sizeof(authorCString)); 99 | memset(titleCString, '\0', sizeof(titleCString)); 100 | 101 | strcpy_s(titleCString, ACTIVITY_BUFFER_SIZE, title.c_str()); 102 | if (timeLeft != LIVESTREAM_TIME_ID) { 103 | strcpy_s(authorCString, ACTIVITY_BUFFER_SIZE, ("by " + author).c_str()); 104 | activityAssets.SetLargeImage("youtube3"); 105 | timeStamp.SetEnd(std::time(nullptr) + timeLeft); 106 | } 107 | else { 108 | if (!(previousTitle == title && previousAuthor == author)) { // MIGHT JUST BE BETTER TO HAVE BACKGROUND.JS SEND THE VIDEO ID 109 | elapsedTime = std::time(nullptr); 110 | } 111 | strcpy_s(authorCString, ACTIVITY_BUFFER_SIZE, ("[LIVE] on " + author).c_str()); 112 | activityAssets.SetLargeImage("youtubelive1"); 113 | timeStamp.SetStart(elapsedTime); 114 | } 115 | 116 | formatCString(titleCString); 117 | formatCString(authorCString); 118 | 119 | activity.SetDetails(titleCString); 120 | activityAssets.SetLargeText(titleCString); 121 | activity.SetState(authorCString); 122 | activityAssets.SetSmallImage("githubmark2"); 123 | activityAssets.SetSmallText("YouTubeDiscordPresence on GitHub by XFG16 (2309#2309)"); // keep this here please, so others can find the extension 124 | 125 | previousTitle = title; 126 | previousAuthor = author; 127 | 128 | bool presenceUpdated = false; 129 | core->ActivityManager().UpdateActivity(activity, [&presenceUpdated](discord::Result result) { 130 | presenceUpdated = true; 131 | }); 132 | int numUpdates = 0; 133 | while (numUpdates < 4 || (numUpdates < 10 && !presenceUpdated)) { 134 | core->RunCallbacks(); 135 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 136 | ++numUpdates; 137 | } 138 | } 139 | 140 | // FORMAT ACTUAL INFORMATION FROM DATA SENT BY EXTENSION 141 | 142 | void handleData(const std::string& documentData) { 143 | size_t titleLocation = documentData.find(TITLE_IDENTIFIER); 144 | size_t authorLocation = documentData.find(AUTHOR_IDENTIFIER); 145 | size_t timeLeftLocation = documentData.find(TIME_LEFT_IDENTIFIER); 146 | size_t endLocation = documentData.length() - END_IDENTIFIER.length(); 147 | 148 | std::string title = documentData.substr(titleLocation + TITLE_IDENTIFIER.length(), authorLocation - (titleLocation + TITLE_IDENTIFIER.length())); 149 | std::string author = documentData.substr(authorLocation + AUTHOR_IDENTIFIER.length(), timeLeftLocation - (authorLocation + AUTHOR_IDENTIFIER.length())); 150 | std::string timeLeft = documentData.substr(timeLeftLocation + TIME_LEFT_IDENTIFIER.length(), endLocation - (timeLeftLocation + TIME_LEFT_IDENTIFIER.length())); 151 | 152 | updatePresence(title, author, timeLeft); 153 | } 154 | 155 | // PROGRAM ENTRY 156 | 157 | int main(void) { 158 | std::string documentData; 159 | if (LOGGING) { 160 | std::cout << "Program initialized" << std::endl; 161 | } 162 | 163 | char temp; 164 | while (std::cin >> std::noskipws >> temp) { 165 | documentData += temp; 166 | if (documentData.length() >= END_IDENTIFIER.length() && documentData.substr(documentData.length() - END_IDENTIFIER.length(), END_IDENTIFIER.length()) == END_IDENTIFIER) { 167 | handleData(documentData); 168 | documentData.clear(); 169 | if (LOGGING) { 170 | std::cout << "DATA CLEARED" << std::endl; 171 | } 172 | } 173 | } 174 | 175 | return 0; 176 | } -------------------------------------------------------------------------------- /Host/Discord/API/activity_manager.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "activity_manager.h" 6 | 7 | #include "core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace discord { 13 | 14 | class ActivityEvents final { 15 | public: 16 | static void OnActivityJoin(void* callbackData, char const* secret) 17 | { 18 | auto* core = reinterpret_cast(callbackData); 19 | if (!core) { 20 | return; 21 | } 22 | 23 | auto& module = core->ActivityManager(); 24 | module.OnActivityJoin(static_cast(secret)); 25 | } 26 | 27 | static void OnActivitySpectate(void* callbackData, char const* secret) 28 | { 29 | auto* core = reinterpret_cast(callbackData); 30 | if (!core) { 31 | return; 32 | } 33 | 34 | auto& module = core->ActivityManager(); 35 | module.OnActivitySpectate(static_cast(secret)); 36 | } 37 | 38 | static void OnActivityJoinRequest(void* callbackData, DiscordUser* user) 39 | { 40 | auto* core = reinterpret_cast(callbackData); 41 | if (!core) { 42 | return; 43 | } 44 | 45 | auto& module = core->ActivityManager(); 46 | module.OnActivityJoinRequest(*reinterpret_cast(user)); 47 | } 48 | 49 | static void OnActivityInvite(void* callbackData, 50 | EDiscordActivityActionType type, 51 | DiscordUser* user, 52 | DiscordActivity* activity) 53 | { 54 | auto* core = reinterpret_cast(callbackData); 55 | if (!core) { 56 | return; 57 | } 58 | 59 | auto& module = core->ActivityManager(); 60 | module.OnActivityInvite(static_cast(type), 61 | *reinterpret_cast(user), 62 | *reinterpret_cast(activity)); 63 | } 64 | }; 65 | 66 | IDiscordActivityEvents ActivityManager::events_{ 67 | &ActivityEvents::OnActivityJoin, 68 | &ActivityEvents::OnActivitySpectate, 69 | &ActivityEvents::OnActivityJoinRequest, 70 | &ActivityEvents::OnActivityInvite, 71 | }; 72 | 73 | Result ActivityManager::RegisterCommand(char const* command) 74 | { 75 | auto result = internal_->register_command(internal_, const_cast(command)); 76 | return static_cast(result); 77 | } 78 | 79 | Result ActivityManager::RegisterSteam(std::uint32_t steamId) 80 | { 81 | auto result = internal_->register_steam(internal_, steamId); 82 | return static_cast(result); 83 | } 84 | 85 | void ActivityManager::UpdateActivity(Activity const& activity, std::function callback) 86 | { 87 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 88 | std::unique_ptr> cb( 89 | reinterpret_cast*>(callbackData)); 90 | if (!cb || !(*cb)) { 91 | return; 92 | } 93 | (*cb)(static_cast(result)); 94 | }; 95 | std::unique_ptr> cb{}; 96 | cb.reset(new std::function(std::move(callback))); 97 | internal_->update_activity(internal_, 98 | reinterpret_cast(const_cast(&activity)), 99 | cb.release(), 100 | wrapper); 101 | } 102 | 103 | void ActivityManager::ClearActivity(std::function callback) 104 | { 105 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 106 | std::unique_ptr> cb( 107 | reinterpret_cast*>(callbackData)); 108 | if (!cb || !(*cb)) { 109 | return; 110 | } 111 | (*cb)(static_cast(result)); 112 | }; 113 | std::unique_ptr> cb{}; 114 | cb.reset(new std::function(std::move(callback))); 115 | internal_->clear_activity(internal_, cb.release(), wrapper); 116 | } 117 | 118 | void ActivityManager::SendRequestReply(UserId userId, 119 | ActivityJoinRequestReply reply, 120 | std::function callback) 121 | { 122 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 123 | std::unique_ptr> cb( 124 | reinterpret_cast*>(callbackData)); 125 | if (!cb || !(*cb)) { 126 | return; 127 | } 128 | (*cb)(static_cast(result)); 129 | }; 130 | std::unique_ptr> cb{}; 131 | cb.reset(new std::function(std::move(callback))); 132 | internal_->send_request_reply(internal_, 133 | userId, 134 | static_cast(reply), 135 | cb.release(), 136 | wrapper); 137 | } 138 | 139 | void ActivityManager::SendInvite(UserId userId, 140 | ActivityActionType type, 141 | char const* content, 142 | std::function callback) 143 | { 144 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 145 | std::unique_ptr> cb( 146 | reinterpret_cast*>(callbackData)); 147 | if (!cb || !(*cb)) { 148 | return; 149 | } 150 | (*cb)(static_cast(result)); 151 | }; 152 | std::unique_ptr> cb{}; 153 | cb.reset(new std::function(std::move(callback))); 154 | internal_->send_invite(internal_, 155 | userId, 156 | static_cast(type), 157 | const_cast(content), 158 | cb.release(), 159 | wrapper); 160 | } 161 | 162 | void ActivityManager::AcceptInvite(UserId userId, std::function callback) 163 | { 164 | static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { 165 | std::unique_ptr> cb( 166 | reinterpret_cast*>(callbackData)); 167 | if (!cb || !(*cb)) { 168 | return; 169 | } 170 | (*cb)(static_cast(result)); 171 | }; 172 | std::unique_ptr> cb{}; 173 | cb.reset(new std::function(std::move(callback))); 174 | internal_->accept_invite(internal_, userId, cb.release(), wrapper); 175 | } 176 | 177 | } // namespace discord 178 | -------------------------------------------------------------------------------- /NodeHost/src/app.js: -------------------------------------------------------------------------------- 1 | // Node.js version of YouTubeDiscordPresence (buttons and no watermark!) 2 | // MAIN VARIABLE INITIALIZATION 3 | 4 | const bundle = require("./bundle"); 5 | const version = "1.4.2"; // CHANGE THIS EVERY UPDATE 6 | 7 | let rpc = require("discord-rpc"); 8 | let client = new rpc.Client({ transport: "ipc" }); 9 | 10 | const LOGGING = true; 11 | 12 | const YT_APP_ID = bundle.YTDP_APPLICATION_ID; 13 | const YT_MUSIC_APP_ID = bundle.YTDP_MUSIC_APPLICATION_ID; 14 | let currentApplication = { 15 | type: "youtube", // "youtube" or "youtubeMusic" 16 | id: YT_APP_ID 17 | } 18 | 19 | const IDLE_MESSAGE = "#*IDLE*#"; 20 | const CSM = "0_SUCCESS"; // CLIENT_SUCCESS_MESSAGE 21 | const CEM = "1_CLIENT_ERROR"; // CLIENT_ERROR_MESSAGE 22 | 23 | // SEND MESSAGE 24 | 25 | function sendNativeVersion() { 26 | let dataObject = { nativeVersion: version }; 27 | let buffer = Buffer.from(JSON.stringify(dataObject)); 28 | let header = Buffer.alloc(4); 29 | 30 | header.writeUInt32LE(buffer.length, 0); 31 | let data = Buffer.concat([header, buffer]); 32 | process.stdout.write(data); 33 | } 34 | 35 | function sendExtensionMessage(success, message, err = null) { 36 | if (!LOGGING) return; 37 | 38 | let formattedMessage = null; 39 | if (success) { 40 | formattedMessage = `${CSM}: ${message}`; 41 | } 42 | else { 43 | if (err) formattedMessage = `${CEM}: ${message}: ${err}`; 44 | else formattedMessage = `${CEM}: ${message}`; 45 | } 46 | 47 | let dataObject = { data: formattedMessage }; 48 | let buffer = Buffer.from(JSON.stringify(dataObject)); 49 | let header = Buffer.alloc(4); 50 | 51 | header.writeUInt32LE(buffer.length, 0); 52 | let data = Buffer.concat([header, buffer]); 53 | process.stdout.write(data); 54 | }; 55 | 56 | // PRESENCE HANDLERS 57 | 58 | async function updatePresence(presenceData, layer) { 59 | let updated = false, errorCaught = false; 60 | setTimeout(() => { 61 | if (layer == 0 && !(updated || errorCaught)) { 62 | client = new rpc.Client({ transport: "ipc" }); 63 | client.login({ clientId: currentApplication.id }).then(() => { 64 | updatePresence(presenceData, 1); 65 | }).catch((loginErr) => { 66 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", loginErr); 67 | }); 68 | } 69 | }, 3000); 70 | 71 | client.request("SET_ACTIVITY", { 72 | pid: process.pid, 73 | activity: presenceData 74 | }).then(() => { 75 | updated = true; 76 | sendExtensionMessage(true, "PRESENCE_UPDATED"); 77 | }).catch((err) => { // IMPORTANT NOTE! Under node_modules/discord-rpc/src/client.js, the RPC_CONNECTION_TIMEOUT was changed from 10e3 to 2000 78 | errorCaught = true; 79 | if (layer == 0) { 80 | client = new rpc.Client({ transport: "ipc" }); 81 | client.login({ clientId: currentApplication.id }).then(() => { 82 | updatePresence(presenceData, 1); 83 | }).catch((loginErr) => { 84 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", loginErr); 85 | }); 86 | } 87 | else { 88 | sendExtensionMessage(false, "PRESENCE_UPDATING_ERROR", err); 89 | } 90 | }); 91 | } 92 | 93 | function clearPresence(callback = null) { 94 | let updated = false, errorCaught = false; 95 | setTimeout(() => { 96 | if (!(updated || errorCaught)) { 97 | client = new rpc.Client({ transport: "ipc" }); 98 | client.login({ clientId: currentApplication.id }).then(() => { 99 | sendExtensionMessage(true, "PRESENCE_CLEARED"); 100 | if (callback) callback(); 101 | }).catch((loginErr) => { 102 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", loginErr); 103 | }); 104 | } 105 | }, 3000); 106 | 107 | client.request("SET_ACTIVITY", { 108 | pid: process.pid, 109 | activity: null 110 | }).then(() => { 111 | updated = true; 112 | sendExtensionMessage(true, "PRESENCE_CLEARED"); 113 | if (callback) callback(); 114 | }).catch((err) => { 115 | errorCaught = true; 116 | client = new rpc.Client({ transport: "ipc" }); 117 | client.login({ clientId: currentApplication.id }).then(() => { 118 | sendExtensionMessage(true, "PRESENCE_CLEARED"); 119 | if (callback) callback(); 120 | }).catch((loginErr) => { 121 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", `${String(err)}, ${String(loginErr)}`); 122 | }); 123 | }); 124 | } 125 | 126 | // CLIENT CONNECTION 127 | 128 | client.on("ready", () => { 129 | sendExtensionMessage(true, "CLIENT_READY"); 130 | }); 131 | 132 | client.login({ clientId: currentApplication.id }).catch((err) => { 133 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", err); 134 | }); 135 | 136 | // // READING DATA FROM BROWSER EXTENSION 137 | // // REFERENCED FROM: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side 138 | 139 | let payloadSize = null; 140 | let chunks = []; 141 | 142 | const sizeHasBeenRead = () => Boolean(payloadSize); 143 | const flushChunksQueue = () => { 144 | payloadSize = null; 145 | chunks.splice(0); 146 | }; 147 | 148 | function handleExtensionPayload(json) { 149 | if (json.jsApplicationType && json.jsApplicationType != currentApplication.type) { 150 | currentApplication.type = json.jsApplicationType; 151 | currentApplication.id = (currentApplication.type == "youtube") ? YT_APP_ID : YT_MUSIC_APP_ID; 152 | 153 | function resetPresence() { 154 | client.destroy().then(() => { 155 | client = new rpc.Client({ transport: "ipc" }); 156 | client.login({ clientId: currentApplication.id }).then(() => { 157 | updatePresence(json.presenceData, 0); 158 | }).catch((err) => { 159 | sendExtensionMessage(false, "CLIENT_CONNECTION_ERROR", err); 160 | }); 161 | }).catch((err) => { 162 | sendExtensionMessage(false, "CLIENT_DESTRUCTION_ERROR", err); 163 | }); 164 | } 165 | clearPresence(resetPresence); 166 | } 167 | else { 168 | updatePresence(json.presenceData, 0); 169 | } 170 | } 171 | 172 | const processData = () => { 173 | const stringData = Buffer.concat(chunks); 174 | if (!sizeHasBeenRead()) payloadSize = stringData.readUInt32LE(0); 175 | 176 | if (stringData.length >= (payloadSize + 4)) { 177 | const contentWithoutSize = stringData.slice(4, (payloadSize + 4)); 178 | flushChunksQueue(); 179 | 180 | try { 181 | const json = JSON.parse(contentWithoutSize); 182 | if (json.presenceData == IDLE_MESSAGE) { 183 | sendExtensionMessage(true, "CLEAR_REQUEST_RECEIVED"); 184 | clearPresence(); 185 | } 186 | else if (json.presenceData) { 187 | sendExtensionMessage(true, "UPDATE_REQUEST_RECEIVED"); 188 | handleExtensionPayload(json) 189 | } 190 | else if (json.getNativeVersion) { 191 | sendExtensionMessage(true, "VERSION_REQUEST_RECEIVED"); 192 | sendNativeVersion(); 193 | } 194 | } 195 | catch (err) { 196 | sendExtensionMessage(false, "FATAL_RUNTIME_ERROR", err); 197 | } 198 | } 199 | }; 200 | 201 | process.stdin.on("readable", () => { 202 | let chunk = null; 203 | while ((chunk = process.stdin.read()) !== null) { 204 | chunks.push(chunk); 205 | } 206 | processData(); 207 | }); 208 | -------------------------------------------------------------------------------- /Extension/content.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022–Present Michael Ren 3 | Licensing and distribution info can be found at the GitHub repository 4 | https://github.com/XFG16/YouTubeDiscordPresence 5 | */ 6 | 7 | // MAIN VARIABLE INITIALIZATION 8 | 9 | const LOGGING = false; 10 | 11 | const VIDEO_ID_SEPARATOR_KEY = "v="; 12 | const PLAYLIST_SEPRATOR_KEY = "&"; 13 | const NORMAL_MESSAGE_DELAY = 1000; 14 | 15 | const AD_SELECTOR = "div.ytp-ad-player-overlay-instream-info"; // DOCUMENT; THIS HAS TO BE DONE BECAUSE IF AN AD PLAYS IN THE MIDDLE OF A VIDEO, THEN GETPLAYERSTATE WILL STILL RETURN 1 16 | const LIVESTREAM_ELEMENT_SELECTOR = "div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-left-controls > div.ytp-time-display.notranslate.ytp-live > button"; // VIDEO PLAYER 17 | const MINIPLAYER_ELEMENT_SELECTOR = "div.ytp-miniplayer-ui"; // VIDEO PLAYER 18 | const MAIN_LIVESTREAM_TITLE_SELECTOR = "div.ytp-chrome-top > div.ytp-title > div.ytp-title-text > a.ytp-title-link"; // VIDEO PLAYER 19 | const MAIN_LIVESTREAM_AUTHOR_SELECTOR = "#upload-info > #channel-name > #container > #text-container > #text > a"; // DOCUMENT HTML 20 | const MINIPLAYER_LIVESTREAM_AUTHOR_SELECTOR = "#video-container #info-bar #owner-name"; // DOCUMENT HTML 21 | const NO_MINIPLAYER_ATTRIBUTE = "display: none;"; 22 | const YES_MINIPLAYER_ATRRIBUTE = ""; 23 | const LIVESTREAM_TIME_ID = -1; 24 | 25 | let documentData = new Object(); 26 | let videoPlayer = document.getElementById("movie_player"); 27 | let shouldSendData = 0; 28 | 29 | // LOGGING 30 | 31 | if (LOGGING) { 32 | console.log("YouTubeDiscordPresence - content.js created"); 33 | } 34 | 35 | // GET VIDEO ID FROM LINK FROM videoPlayer.getVideoUrl() 36 | 37 | function getVideoId(url) { 38 | if (url.includes(VIDEO_ID_SEPARATOR_KEY)) { 39 | return url.split(VIDEO_ID_SEPARATOR_KEY)[1] 40 | } 41 | return null; 42 | } 43 | 44 | // WEB REQUEST FOR VIDEO DATA (https://stackoverflow.com/questions/2499567/how-to-make-a-json-call-to-an-url/2499647#2499647) 45 | // ALSO SEE OEMBED DETAILS: (https://stackoverflow.com/questions/30084140/youtube-video-title-with-api-v3-without-api-key) 46 | 47 | const getOEmbedJSON = async videoId => { 48 | const response = await fetch("https://www.youtube.com/oembed?url=http%3A//youtube.com/watch%3Fv%3D" + videoId + "&format=json"); 49 | if (!response.ok) { 50 | throw new Error(response.statusText); 51 | } 52 | const data = response.json(); 53 | return data; 54 | } 55 | 56 | // DOCUMENT SCANNING IF VIDEO IS LIVESTREAM OR OEMBED DOESN'T WORK (https://developers.google.com/youtube/iframe_api_reference) 57 | // ALSO SEE (https://stackoverflow.com/questions/9515704/use-a-content-script-to-access-the-page-context-letiables-and-functions) 58 | 59 | function getLivestreamData() { 60 | let miniplayerHTML = videoPlayer.querySelector(MINIPLAYER_ELEMENT_SELECTOR); 61 | if (!miniplayerHTML || (miniplayerHTML && miniplayerHTML.getAttribute("style") == NO_MINIPLAYER_ATTRIBUTE)) { 62 | let titleHTML = videoPlayer.querySelector(MAIN_LIVESTREAM_TITLE_SELECTOR); 63 | let authorHTML = document.querySelector(MAIN_LIVESTREAM_AUTHOR_SELECTOR); 64 | if (titleHTML) { 65 | documentData.title = titleHTML.innerText; 66 | } 67 | else { 68 | documentData.title = null; 69 | } 70 | if (authorHTML) { 71 | documentData.author = authorHTML.innerText; 72 | documentData.channelUrl = authorHTML.href; 73 | } 74 | else { 75 | documentData.author = null; 76 | } 77 | } 78 | else if (miniplayerHTML && miniplayerHTML.getAttribute("style") == YES_MINIPLAYER_ATRRIBUTE) { 79 | let titleHTML = videoPlayer.querySelector(MAIN_LIVESTREAM_TITLE_SELECTOR); 80 | let authorHTML = document.querySelector(MINIPLAYER_LIVESTREAM_AUTHOR_SELECTOR); 81 | if (titleHTML) { 82 | documentData.title = titleHTML.innerText; 83 | } 84 | else { 85 | documentData.title = null; 86 | } 87 | if (authorHTML) { 88 | documentData.author = authorHTML.innerText; 89 | documentData.channelUrl = authorHTML.href; 90 | } 91 | else { 92 | documentData.author = null; 93 | } 94 | } 95 | } 96 | 97 | // SEPARATE FUNCTION FOR GETTING VIDEO TIMES 98 | // HAS TO BE A SEPARATE FUNCTION BECAUSE THE OEMBED REQUEST IS ASYNCHRONOUS, WHICH CAN CAUSE THE PRESENCE TO DISPLAY THE WRONG TIME IF PUT INTO THE MAIN FUNCTION DIRECTLY 99 | 100 | function getTimeData() { 101 | if (videoPlayer.getDuration() && videoPlayer.getCurrentTime()) { 102 | documentData.duration = videoPlayer.getDuration(); 103 | documentData.timeLeft = documentData.duration - videoPlayer.getCurrentTime(); 104 | if (documentData.timeLeft < 0) { 105 | documentData.timeLeft = null; 106 | } 107 | } 108 | else { 109 | documentData.timeLeft = null; 110 | console.log("Unable to get timestamp data for YouTubeDiscordPresence"); 111 | } 112 | } 113 | 114 | // SEPARATE FUNCTION FOR COMMUNICATION TO PREVENT ASYNC FROM CAUSING WRONG DATA TO GET SENT 115 | 116 | function sendDocumentData() { 117 | if (documentData.title && documentData.author && documentData.timeLeft) { 118 | if (documentData.author.endsWith(" - Topic")) { 119 | documentData.author = documentData.author.slice(0, -8); 120 | } 121 | let messageEvent = new CustomEvent("SendToLoader", { detail: documentData }); 122 | window.dispatchEvent(messageEvent); 123 | } 124 | } 125 | 126 | // SEPARATE FUNCTION FOR LIVESTREAM DATA OR MINIPLAYERS THAT INCLUDE THE AUTHOR 127 | 128 | function handleYouTubeData() { 129 | let livestreamHTML = videoPlayer.querySelector(LIVESTREAM_ELEMENT_SELECTOR); 130 | documentData.videoId = getVideoId(videoPlayer.getVideoUrl()); 131 | documentData.applicationType = window.location.href.includes("music.youtube") ? "youtubeMusic" : "youtube"; 132 | 133 | if (documentData.applicationType == "youtubeMusic") { // GRABS YT MUSIC ALBUM THUMBNAIL 134 | let thumbnail = document.querySelector("#song-image #thumbnail #img"); 135 | if (thumbnail && "src" in thumbnail && thumbnail.src.startsWith("https://lh3.googleusercontent.com/")) { 136 | documentData.thumbnailUrl = thumbnail.src; 137 | } 138 | else { 139 | documentData.thumbnailUrl = `https://i.ytimg.com/vi/${documentData.videoId}/hqdefault.jpg`; 140 | } 141 | } 142 | else { 143 | documentData.thumbnailUrl = `https://i.ytimg.com/vi/${documentData.videoId}/hqdefault.jpg`; 144 | } 145 | 146 | if (!livestreamHTML) { 147 | getOEmbedJSON(documentData.videoId).then(data => { // TRY USING OEMBED FIRST 148 | documentData.title = data.title; 149 | documentData.author = data.author_name; 150 | documentData.channelUrl = data.author_url; 151 | getTimeData(); 152 | sendDocumentData(); 153 | }).catch(error => { // IF THAT DOESN'T WORK, USE SELECTORS 154 | getLivestreamData(); 155 | getTimeData(); 156 | sendDocumentData(); 157 | console.error(error); 158 | }); 159 | } 160 | else { // ALWAYS USE SELECTORS FOR LIVESTREAMS 161 | getLivestreamData(); 162 | documentData.timeLeft = LIVESTREAM_TIME_ID; 163 | sendDocumentData(); 164 | } 165 | } 166 | 167 | // SENDER OF DATA TO CONTENT_LOADER.JS FOR REDIRECTION TO BACKGROUND.JS 168 | 169 | setInterval(function () { 170 | if (!videoPlayer) { 171 | videoPlayer = document.getElementById("movie_player"); 172 | } 173 | if (videoPlayer && videoPlayer.getPlayerState() == 1 && document.querySelector(AD_SELECTOR) == null) { 174 | handleYouTubeData(); 175 | } 176 | }, NORMAL_MESSAGE_DELAY); -------------------------------------------------------------------------------- /NodeHost/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-discord-presence", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "youtube-discord-presence", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "discord-rpc": "^4.0.1" 13 | } 14 | }, 15 | "node_modules/bindings": { 16 | "version": "1.5.0", 17 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 18 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 19 | "optional": true, 20 | "dependencies": { 21 | "file-uri-to-path": "1.0.0" 22 | } 23 | }, 24 | "node_modules/discord-rpc": { 25 | "version": "4.0.1", 26 | "resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz", 27 | "integrity": "sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==", 28 | "dependencies": { 29 | "node-fetch": "^2.6.1", 30 | "ws": "^7.3.1" 31 | }, 32 | "optionalDependencies": { 33 | "register-scheme": "github:devsnek/node-register-scheme" 34 | } 35 | }, 36 | "node_modules/discord-rpc/node_modules/node-fetch": { 37 | "version": "2.6.7", 38 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 39 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 40 | "dependencies": { 41 | "whatwg-url": "^5.0.0" 42 | }, 43 | "engines": { 44 | "node": "4.x || >=6.0.0" 45 | }, 46 | "peerDependencies": { 47 | "encoding": "^0.1.0" 48 | }, 49 | "peerDependenciesMeta": { 50 | "encoding": { 51 | "optional": true 52 | } 53 | } 54 | }, 55 | "node_modules/file-uri-to-path": { 56 | "version": "1.0.0", 57 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 58 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 59 | "optional": true 60 | }, 61 | "node_modules/node-addon-api": { 62 | "version": "1.7.2", 63 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", 64 | "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", 65 | "optional": true 66 | }, 67 | "node_modules/register-scheme": { 68 | "version": "0.0.2", 69 | "resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17", 70 | "integrity": "sha512-0VPwdfz8BiMeR+UAJyXB1DMfSdgRFk0zrgtpnDJAtFUZYA7ofF2UhqLcWIPv0Fvf31+Tw7uxJVxdMuN3AyZ2mQ==", 71 | "hasInstallScript": true, 72 | "license": "MIT", 73 | "optional": true, 74 | "dependencies": { 75 | "bindings": "^1.3.0", 76 | "node-addon-api": "^1.3.0" 77 | } 78 | }, 79 | "node_modules/tr46": { 80 | "version": "0.0.3", 81 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 82 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 83 | }, 84 | "node_modules/webidl-conversions": { 85 | "version": "3.0.1", 86 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 87 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 88 | }, 89 | "node_modules/whatwg-url": { 90 | "version": "5.0.0", 91 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 92 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 93 | "dependencies": { 94 | "tr46": "~0.0.3", 95 | "webidl-conversions": "^3.0.0" 96 | } 97 | }, 98 | "node_modules/ws": { 99 | "version": "7.5.9", 100 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", 101 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", 102 | "engines": { 103 | "node": ">=8.3.0" 104 | }, 105 | "peerDependencies": { 106 | "bufferutil": "^4.0.1", 107 | "utf-8-validate": "^5.0.2" 108 | }, 109 | "peerDependenciesMeta": { 110 | "bufferutil": { 111 | "optional": true 112 | }, 113 | "utf-8-validate": { 114 | "optional": true 115 | } 116 | } 117 | } 118 | }, 119 | "dependencies": { 120 | "bindings": { 121 | "version": "1.5.0", 122 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 123 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 124 | "optional": true, 125 | "requires": { 126 | "file-uri-to-path": "1.0.0" 127 | } 128 | }, 129 | "discord-rpc": { 130 | "version": "4.0.1", 131 | "resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz", 132 | "integrity": "sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==", 133 | "requires": { 134 | "node-fetch": "^2.6.1", 135 | "register-scheme": "github:devsnek/node-register-scheme", 136 | "ws": "^7.3.1" 137 | }, 138 | "dependencies": { 139 | "node-fetch": { 140 | "version": "2.6.7", 141 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 142 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 143 | "requires": { 144 | "whatwg-url": "^5.0.0" 145 | } 146 | } 147 | } 148 | }, 149 | "file-uri-to-path": { 150 | "version": "1.0.0", 151 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 152 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 153 | "optional": true 154 | }, 155 | "node-addon-api": { 156 | "version": "1.7.2", 157 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", 158 | "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", 159 | "optional": true 160 | }, 161 | "register-scheme": { 162 | "version": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17", 163 | "integrity": "sha512-0VPwdfz8BiMeR+UAJyXB1DMfSdgRFk0zrgtpnDJAtFUZYA7ofF2UhqLcWIPv0Fvf31+Tw7uxJVxdMuN3AyZ2mQ==", 164 | "from": "register-scheme@github:devsnek/node-register-scheme", 165 | "optional": true, 166 | "requires": { 167 | "bindings": "^1.3.0", 168 | "node-addon-api": "^1.3.0" 169 | } 170 | }, 171 | "tr46": { 172 | "version": "0.0.3", 173 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 174 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 175 | }, 176 | "webidl-conversions": { 177 | "version": "3.0.1", 178 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 179 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 180 | }, 181 | "whatwg-url": { 182 | "version": "5.0.0", 183 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 184 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 185 | "requires": { 186 | "tr46": "~0.0.3", 187 | "webidl-conversions": "^3.0.0" 188 | } 189 | }, 190 | "ws": { 191 | "version": "7.5.9", 192 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", 193 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", 194 | "requires": {} 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 | If you're here from the Chrome Web Store, **you can skip the first step.** 11 | 12 | 1. Add the [**Chrome Extension**](https://chrome.google.com/webstore/detail/youtubediscordpresence/hnmeidgkfcbpjjjpmjmpehjdljlaeaaa) from the Chrome Web Store 13 | 14 | - To access the personalization page, click the small icon in the top right corner of the browser, located beneath the extensions icon. For easier access, consider pinning the extension. 15 | 16 | 2. Download the latest `YTDPsetup.msi` file in the [**releases**](https://github.com/XFG16/YouTubeDiscordPresence/releases/tag/1.4.2) section of this repository and **run it on your device** to install the secondary desktop component. 17 | 18 | - **NOTE:** Only Windows (x64) versions are currently supported. 19 | 20 | Still confused? Watch the **installation tutorial** on YouTube using [**this link**](https://www.youtube.com/watch?v=BWPNqPGFyL4). 21 | 22 | --- 23 | 24 | > [!IMPORTANT] 25 | > Please read the announcements below for how the recent Discord UI/UX changes have impacted YouTubeDiscordPresence. **Don't worry, YouTubeDiscordPresence will still work, but there will be some minor changes to how the rich presence is diplayed.** 26 | 27 | # Announcements 28 | 29 | Recently, Discord has been making major UI/UX changes that have impacted YouTubeDiscordPresence. **Here are a few things you should know:** 30 | 31 | 1. **Buttons:** Don't worry, Discord did not remove this feature. However, buttons on your profile will no longer show on your client. Instead, they will only be displayed on other people's clients, so everyone else will still be able to click the buttons on your profile. If you want proof, simply ask a friend to take a screenshot after you start watching YouTube or YouTube Music. 32 | 33 | - Here's [**an example**](https://github.com/discordjs/RPC/issues/180#issuecomment-2313232518). 34 | 35 | 2. **Time Left:** For whatever reason, Discord deprecated the ability to display time left in a video. As a workaround, YouTubeDiscordPresence will now display how far you are into the video. I will work on addressing this once Discord finalizes its UI/UX updates. 36 | 37 | - An example profile with all the features with new UI/UX updates is shown below. **Again, note that while you might not see all features on your client, this is what everyone else's client would display for your profile.** 38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 47 | These issues are **not unique to YouTubeDiscordPresence.** For example, you can no longer see Spotify buttons on your own client either. 48 | 49 | --- 50 | 51 | # YouTubeDiscordPresence for Windows (x64) 52 | 53 |

54 | 55 | 56 | 57 | 58 |

59 | 60 | - This is an extension used to create a detailed rich presence for YouTube and YouTube Music on Discord. It is a project that I decided to take on towards the end of my freshman year. 61 | 62 | - Currently, the application only supports **Windows (x64)**, although more operating systems will be supported in the future. Stay tuned! 63 | 64 | - Any warnings from services like VirusTotal are **false positives** due to YouTubeDiscordPresence being compiled using [**pkg**](https://github.com/vercel/pkg), a widely-used app bundler. 65 | 66 | --- 67 | 68 | ## Troubleshooting/Known Issues 69 | 70 | > [!IMPORTANT] 71 | > Please read the announcements above for how the recent Discord UI/UX changes have impacted YouTubeDiscordPresence. **Don't worry, YouTubeDiscordPresence will still work, but there will be some minor changes to how the rich presence is diplayed.** 72 | 73 | Otherwise, see if any of the following address your issue: 74 | 75 | - YouTubeDiscordPresence only works with the desktop application of Discord, **not the browser version.** 76 | 77 | - Please ensure that `Display current activity as status message` in your Discord settings is **turned on.** 78 | 79 | - The appearance and disappearance of the rich presence on your profile **can be delayed** because Discord limits the processing of rich presence update requests to once every 15 seconds. 80 | 81 | - The rich presence can also randomly disappear and reappear within a few seconds because Chrome forcibly unloads and reloads the `background.js` in Manifest v3 82 | 83 | If none of the above address your issue, then the first step you should always take is to go to `chrome://extensions` and disable the extension. Then, close and reopen your browser, and re-enable the extension, especially... 84 | 85 | - If the extension is **not appearing** even after you installed the desktop application... 86 | 87 | - In this case, your Discord client is likely ratelimiting YouTubeDiscordPresence. To fix this, **do not simply just reload Discord. Go to your system tray or task manager and quit Discord before relaunching it.** 88 | 89 | - If **two or more instances of the rich presence** appear on your profile... 90 | 91 | - Again, this is an error with the socket implementation Discord currently has and there is currently no easy way around it. 92 | 93 | --- 94 | 95 | ## Opening a GitHub Issue 96 | 97 | - If you need more details and have the ability to open an issue, then before that, please head to `chrome://extensions`, **turn on developer mode**, and click **"inspect views service worker"**. This should open a developer window. From there, head to the **console** section and describe what the debug log shows. 98 | 99 | - Don't hesistate to open an issue if there's something wrong with YouTubeDiscordPresence. In fact, you should also open one if you have any suggestions for a new feature to be added. 100 | 101 | --- 102 | 103 | ## Detailed Installation Instructions 104 | 105 | 1. Building the installer from scratch: 106 | 107 | - For NodeJS version: download the `NodeHost` directory and use [**pkg**](https://github.com/vercel/pkg) to compile `app.js` into an executable. However, you have to link the Chrome extension to the compiled executable manually, which can be done by following [**this guide**](https://developer.chrome.com/docs/apps/nativeMessaging/) 108 | 109 | - Note that in `node_modules/discord-rpc/src/client.js`, the `RPC_CONNECTION_TIMEOUT` was changed from `10e3` to `2000` 110 | 111 | - The `bundle.js` file contains the application IDs for the YouTube and YouTube Music rich presence that you have to create separately in the [**Discord Developer Portal**](https://discord.com/developers/applications). Make sure the image keys match the ones in `app.js` 112 | 113 | - For C++ version: you can **build** the whole thing yourself with **Visual Studio 2022**. Just download the `Host` directory from this repository and open `YTDPwin.sln` under `Host/YTDPwin` in Visual Studio. Also, make sure to have the **Microsoft Visual Studio Installer Project** extension installed 114 | 115 | 2. Add the [**Chrome Extension**](https://chrome.google.com/webstore/detail/youtubediscordpresence/hnmeidgkfcbpjjjpmjmpehjdljlaeaaa) from the **Chrome Web Store** 116 | 117 | - If you want to load the extension without the Chrome Web Store or make edits, download the `Extension` directory, compress it into a zip, and load it onto your browser manually. 118 | 119 | - Make sure that the `"allowed_origins"` key in the JSON file involved in [**native messaging**](https://developer.chrome.com/docs/apps/nativeMessaging/) contains the extension's ID. This file can be found in the location you installed YouTubeDiscordPresence, which is usually `C:\Program Files\YouTubeDiscordPresence` as `main.json` 120 | 121 | --- 122 | 123 | ## Miscellaneous 124 | 125 | **DISCLAIMER:** this is not a bootleg copy of PreMiD. On a more technical note, it works similar to the Spotify rich presence—it only appears **when a video is playing** and **disappears when there is no video or the video is paused**. In addition, it only displays the presence for videos. Idling and searching are **not displayed**. Features such as exclusions, fully customizable details, and thumbnail coverage are **unique and original** to YouTubeDiscordPresence. YouTubeDiscordPresence has not referenced nor is affiliated with PreMiD in any way whatsoever. 126 | 127 | --- 128 | 129 | ## License 130 | 131 | Licensed under the [MIT](https://github.com/XFG16/YouTubeDiscordPresence/blob/main/LICENSE.txt) license. 132 | -------------------------------------------------------------------------------- /Host/YTDPwin/YTDPwin/YTDPwin.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 16.0 58 | Win32Proj 59 | {fe7ad499-27b7-4135-af92-20e7e3984fc3} 60 | YTDPwin 61 | 10.0 62 | YTDPwin 63 | 64 | 65 | 66 | Application 67 | true 68 | v143 69 | Unicode 70 | 71 | 72 | Application 73 | false 74 | v143 75 | true 76 | Unicode 77 | 78 | 79 | Application 80 | true 81 | v143 82 | Unicode 83 | 84 | 85 | Application 86 | false 87 | v143 88 | true 89 | Unicode 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | true 111 | 112 | 113 | false 114 | 115 | 116 | true 117 | 118 | 119 | false 120 | 121 | 122 | 123 | Level3 124 | true 125 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 126 | true 127 | 128 | 129 | Console 130 | true 131 | 132 | 133 | 134 | 135 | Level3 136 | true 137 | true 138 | true 139 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 140 | true 141 | 142 | 143 | Console 144 | true 145 | true 146 | true 147 | 148 | 149 | 150 | 151 | Level3 152 | true 153 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 154 | true 155 | 156 | 157 | Console 158 | true 159 | ..\..\Discord\Lib\x86_64;%(AdditionalLibraryDirectories) 160 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);..\..\Discord\Lib\discord_game_sdk.dll.lib 161 | 162 | 163 | 164 | 165 | Level3 166 | true 167 | true 168 | true 169 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 170 | true 171 | 172 | 173 | Console 174 | true 175 | true 176 | true 177 | ..\..\Discord\Lib\x86_64;%(AdditionalLibraryDirectories) 178 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);..\..\Discord\Lib\discord_game_sdk.dll.lib 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /Host/Discord/API/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ffi.h" 4 | #include "event.h" 5 | 6 | namespace discord { 7 | 8 | enum class Result { 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 | }; 54 | 55 | enum class CreateFlags { 56 | Default = 0, 57 | NoRequireDiscord = 1, 58 | }; 59 | 60 | enum class LogLevel { 61 | Error = 1, 62 | Warn, 63 | Info, 64 | Debug, 65 | }; 66 | 67 | enum class UserFlag { 68 | Partner = 2, 69 | HypeSquadEvents = 4, 70 | HypeSquadHouse1 = 64, 71 | HypeSquadHouse2 = 128, 72 | HypeSquadHouse3 = 256, 73 | }; 74 | 75 | enum class PremiumType { 76 | None = 0, 77 | Tier1 = 1, 78 | Tier2 = 2, 79 | }; 80 | 81 | enum class ImageType { 82 | User, 83 | }; 84 | 85 | enum class ActivityType { 86 | Playing, 87 | Streaming, 88 | Listening, 89 | Watching, 90 | }; 91 | 92 | enum class ActivityActionType { 93 | Join = 1, 94 | Spectate, 95 | }; 96 | 97 | enum class ActivityJoinRequestReply { 98 | No, 99 | Yes, 100 | Ignore, 101 | }; 102 | 103 | enum class Status { 104 | Offline = 0, 105 | Online = 1, 106 | Idle = 2, 107 | DoNotDisturb = 3, 108 | }; 109 | 110 | enum class RelationshipType { 111 | None, 112 | Friend, 113 | Blocked, 114 | PendingIncoming, 115 | PendingOutgoing, 116 | Implicit, 117 | }; 118 | 119 | enum class LobbyType { 120 | Private = 1, 121 | Public, 122 | }; 123 | 124 | enum class LobbySearchComparison { 125 | LessThanOrEqual = -2, 126 | LessThan, 127 | Equal, 128 | GreaterThan, 129 | GreaterThanOrEqual, 130 | NotEqual, 131 | }; 132 | 133 | enum class LobbySearchCast { 134 | String = 1, 135 | Number, 136 | }; 137 | 138 | enum class LobbySearchDistance { 139 | Local, 140 | Default, 141 | Extended, 142 | Global, 143 | }; 144 | 145 | enum class EntitlementType { 146 | Purchase = 1, 147 | PremiumSubscription, 148 | DeveloperGift, 149 | TestModePurchase, 150 | FreePurchase, 151 | UserGift, 152 | PremiumPurchase, 153 | }; 154 | 155 | enum class SkuType { 156 | Application = 1, 157 | DLC, 158 | Consumable, 159 | Bundle, 160 | }; 161 | 162 | enum class InputModeType { 163 | VoiceActivity = 0, 164 | PushToTalk, 165 | }; 166 | 167 | using ClientId = std::int64_t; 168 | using Version = std::int32_t; 169 | using Snowflake = std::int64_t; 170 | using Timestamp = std::int64_t; 171 | using UserId = Snowflake; 172 | using Locale = char const*; 173 | using Branch = char const*; 174 | using LobbyId = Snowflake; 175 | using LobbySecret = char const*; 176 | using MetadataKey = char const*; 177 | using MetadataValue = char const*; 178 | using NetworkPeerId = std::uint64_t; 179 | using NetworkChannelId = std::uint8_t; 180 | using Path = char const*; 181 | using DateTime = char const*; 182 | 183 | class User final { 184 | public: 185 | void SetId(UserId id); 186 | UserId GetId() const; 187 | void SetUsername(char const* username); 188 | char const* GetUsername() const; 189 | void SetDiscriminator(char const* discriminator); 190 | char const* GetDiscriminator() const; 191 | void SetAvatar(char const* avatar); 192 | char const* GetAvatar() const; 193 | void SetBot(bool bot); 194 | bool GetBot() const; 195 | 196 | private: 197 | DiscordUser internal_; 198 | }; 199 | 200 | class OAuth2Token final { 201 | public: 202 | void SetAccessToken(char const* accessToken); 203 | char const* GetAccessToken() const; 204 | void SetScopes(char const* scopes); 205 | char const* GetScopes() const; 206 | void SetExpires(Timestamp expires); 207 | Timestamp GetExpires() const; 208 | 209 | private: 210 | DiscordOAuth2Token internal_; 211 | }; 212 | 213 | class ImageHandle final { 214 | public: 215 | void SetType(ImageType type); 216 | ImageType GetType() const; 217 | void SetId(std::int64_t id); 218 | std::int64_t GetId() const; 219 | void SetSize(std::uint32_t size); 220 | std::uint32_t GetSize() const; 221 | 222 | private: 223 | DiscordImageHandle internal_; 224 | }; 225 | 226 | class ImageDimensions final { 227 | public: 228 | void SetWidth(std::uint32_t width); 229 | std::uint32_t GetWidth() const; 230 | void SetHeight(std::uint32_t height); 231 | std::uint32_t GetHeight() const; 232 | 233 | private: 234 | DiscordImageDimensions internal_; 235 | }; 236 | 237 | class ActivityTimestamps final { 238 | public: 239 | void SetStart(Timestamp start); 240 | Timestamp GetStart() const; 241 | void SetEnd(Timestamp end); 242 | Timestamp GetEnd() const; 243 | 244 | private: 245 | DiscordActivityTimestamps internal_; 246 | }; 247 | 248 | class ActivityAssets final { 249 | public: 250 | void SetLargeImage(char const* largeImage); 251 | char const* GetLargeImage() const; 252 | void SetLargeText(char const* largeText); 253 | char const* GetLargeText() const; 254 | void SetSmallImage(char const* smallImage); 255 | char const* GetSmallImage() const; 256 | void SetSmallText(char const* smallText); 257 | char const* GetSmallText() const; 258 | 259 | private: 260 | DiscordActivityAssets internal_; 261 | }; 262 | 263 | class PartySize final { 264 | public: 265 | void SetCurrentSize(std::int32_t currentSize); 266 | std::int32_t GetCurrentSize() const; 267 | void SetMaxSize(std::int32_t maxSize); 268 | std::int32_t GetMaxSize() const; 269 | 270 | private: 271 | DiscordPartySize internal_; 272 | }; 273 | 274 | class ActivityParty final { 275 | public: 276 | void SetId(char const* id); 277 | char const* GetId() const; 278 | PartySize& GetSize(); 279 | PartySize const& GetSize() const; 280 | 281 | private: 282 | DiscordActivityParty internal_; 283 | }; 284 | 285 | class ActivitySecrets final { 286 | public: 287 | void SetMatch(char const* match); 288 | char const* GetMatch() const; 289 | void SetJoin(char const* join); 290 | char const* GetJoin() const; 291 | void SetSpectate(char const* spectate); 292 | char const* GetSpectate() const; 293 | 294 | private: 295 | DiscordActivitySecrets internal_; 296 | }; 297 | 298 | class Activity final { 299 | public: 300 | void SetType(ActivityType type); 301 | ActivityType GetType() const; 302 | void SetApplicationId(std::int64_t applicationId); 303 | std::int64_t GetApplicationId() const; 304 | void SetName(char const* name); 305 | char const* GetName() const; 306 | void SetState(char const* state); 307 | char const* GetState() const; 308 | void SetDetails(char const* details); 309 | char const* GetDetails() const; 310 | ActivityTimestamps& GetTimestamps(); 311 | ActivityTimestamps const& GetTimestamps() const; 312 | ActivityAssets& GetAssets(); 313 | ActivityAssets const& GetAssets() const; 314 | ActivityParty& GetParty(); 315 | ActivityParty const& GetParty() const; 316 | ActivitySecrets& GetSecrets(); 317 | ActivitySecrets const& GetSecrets() const; 318 | void SetInstance(bool instance); 319 | bool GetInstance() const; 320 | 321 | private: 322 | DiscordActivity internal_; 323 | }; 324 | 325 | class Presence final { 326 | public: 327 | void SetStatus(Status status); 328 | Status GetStatus() const; 329 | Activity& GetActivity(); 330 | Activity const& GetActivity() const; 331 | 332 | private: 333 | DiscordPresence internal_; 334 | }; 335 | 336 | class Relationship final { 337 | public: 338 | void SetType(RelationshipType type); 339 | RelationshipType GetType() const; 340 | User& GetUser(); 341 | User const& GetUser() const; 342 | Presence& GetPresence(); 343 | Presence const& GetPresence() const; 344 | 345 | private: 346 | DiscordRelationship internal_; 347 | }; 348 | 349 | class Lobby final { 350 | public: 351 | void SetId(LobbyId id); 352 | LobbyId GetId() const; 353 | void SetType(LobbyType type); 354 | LobbyType GetType() const; 355 | void SetOwnerId(UserId ownerId); 356 | UserId GetOwnerId() const; 357 | void SetSecret(LobbySecret secret); 358 | LobbySecret GetSecret() const; 359 | void SetCapacity(std::uint32_t capacity); 360 | std::uint32_t GetCapacity() const; 361 | void SetLocked(bool locked); 362 | bool GetLocked() const; 363 | 364 | private: 365 | DiscordLobby internal_; 366 | }; 367 | 368 | class FileStat final { 369 | public: 370 | void SetFilename(char const* filename); 371 | char const* GetFilename() const; 372 | void SetSize(std::uint64_t size); 373 | std::uint64_t GetSize() const; 374 | void SetLastModified(std::uint64_t lastModified); 375 | std::uint64_t GetLastModified() const; 376 | 377 | private: 378 | DiscordFileStat internal_; 379 | }; 380 | 381 | class Entitlement final { 382 | public: 383 | void SetId(Snowflake id); 384 | Snowflake GetId() const; 385 | void SetType(EntitlementType type); 386 | EntitlementType GetType() const; 387 | void SetSkuId(Snowflake skuId); 388 | Snowflake GetSkuId() const; 389 | 390 | private: 391 | DiscordEntitlement internal_; 392 | }; 393 | 394 | class SkuPrice final { 395 | public: 396 | void SetAmount(std::uint32_t amount); 397 | std::uint32_t GetAmount() const; 398 | void SetCurrency(char const* currency); 399 | char const* GetCurrency() const; 400 | 401 | private: 402 | DiscordSkuPrice internal_; 403 | }; 404 | 405 | class Sku final { 406 | public: 407 | void SetId(Snowflake id); 408 | Snowflake GetId() const; 409 | void SetType(SkuType type); 410 | SkuType GetType() const; 411 | void SetName(char const* name); 412 | char const* GetName() const; 413 | SkuPrice& GetPrice(); 414 | SkuPrice const& GetPrice() const; 415 | 416 | private: 417 | DiscordSku internal_; 418 | }; 419 | 420 | class InputMode final { 421 | public: 422 | void SetType(InputModeType type); 423 | InputModeType GetType() const; 424 | void SetShortcut(char const* shortcut); 425 | char const* GetShortcut() const; 426 | 427 | private: 428 | DiscordInputMode internal_; 429 | }; 430 | 431 | class UserAchievement final { 432 | public: 433 | void SetUserId(Snowflake userId); 434 | Snowflake GetUserId() const; 435 | void SetAchievementId(Snowflake achievementId); 436 | Snowflake GetAchievementId() const; 437 | void SetPercentComplete(std::uint8_t percentComplete); 438 | std::uint8_t GetPercentComplete() const; 439 | void SetUnlockedAt(DateTime unlockedAt); 440 | DateTime GetUnlockedAt() const; 441 | 442 | private: 443 | DiscordUserAchievement internal_; 444 | }; 445 | 446 | class LobbyTransaction final { 447 | public: 448 | Result SetType(LobbyType type); 449 | Result SetOwner(UserId ownerId); 450 | Result SetCapacity(std::uint32_t capacity); 451 | Result SetMetadata(MetadataKey key, MetadataValue value); 452 | Result DeleteMetadata(MetadataKey key); 453 | Result SetLocked(bool locked); 454 | 455 | IDiscordLobbyTransaction** Receive() { return &internal_; } 456 | IDiscordLobbyTransaction* Internal() { return internal_; } 457 | 458 | private: 459 | IDiscordLobbyTransaction* internal_; 460 | }; 461 | 462 | class LobbyMemberTransaction final { 463 | public: 464 | Result SetMetadata(MetadataKey key, MetadataValue value); 465 | Result DeleteMetadata(MetadataKey key); 466 | 467 | IDiscordLobbyMemberTransaction** Receive() { return &internal_; } 468 | IDiscordLobbyMemberTransaction* Internal() { return internal_; } 469 | 470 | private: 471 | IDiscordLobbyMemberTransaction* internal_; 472 | }; 473 | 474 | class LobbySearchQuery final { 475 | public: 476 | Result Filter(MetadataKey key, 477 | LobbySearchComparison comparison, 478 | LobbySearchCast cast, 479 | MetadataValue value); 480 | Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value); 481 | Result Limit(std::uint32_t limit); 482 | Result Distance(LobbySearchDistance distance); 483 | 484 | IDiscordLobbySearchQuery** Receive() { return &internal_; } 485 | IDiscordLobbySearchQuery* Internal() { return internal_; } 486 | 487 | private: 488 | IDiscordLobbySearchQuery* internal_; 489 | }; 490 | 491 | } // namespace discord 492 | -------------------------------------------------------------------------------- /Extension/popup.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022–Present Michael Ren 3 | Licensing and distribution info can be found at the GitHub repository 4 | https://github.com/XFG16/YouTubeDiscordPresence 5 | */ 6 | 7 | * { 8 | margin: 0px; 9 | padding: 0px; 10 | } 11 | 12 | html, 13 | body { 14 | width: 404px; 15 | height: 500px; 16 | background-color: rgb(40, 40, 40); 17 | margin: 0px; 18 | display: block; 19 | position: relative; 20 | overflow-y: scroll; 21 | overflow-x: hidden; 22 | } 23 | 24 | ::-webkit-scrollbar { 25 | width: 4px; 26 | } 27 | 28 | ::-webkit-scrollbar-track { 29 | background: rgb(128, 128, 128); 30 | } 31 | 32 | ::-webkit-scrollbar-thumb { 33 | background-color: rgb(215, 215, 215); 34 | border-radius: 5px; 35 | } 36 | 37 | ::-webkit-scrollbar-thumb:hover { 38 | background-color: rgb(170, 170, 170); 39 | } 40 | 41 | #ytdpSettingsTitle { 42 | width: 100%; 43 | height: auto; 44 | margin: 1px; 45 | margin-bottom: -3.5px; 46 | } 47 | 48 | #clientErrorContainer, #osIncompatibleContainer { 49 | background-color: rgb(214, 40, 40); 50 | padding-top: 10px; 51 | padding-bottom: 10px; 52 | padding-left: 20px; 53 | padding-right: 20px; 54 | color: rgb(240, 240, 240); 55 | font-size: 16px; 56 | } 57 | 58 | #clientErrorContainer h1, #osIncompatibleContainer h1 { 59 | font-size: 19px; 60 | padding-bottom: 6px; 61 | border-bottom: solid 2px white; 62 | margin-bottom: 5px; 63 | width: fit-content; 64 | } 65 | 66 | #clientErrorContainer h6, #osIncompatibleContainer h6 { 67 | margin-top: 10px; 68 | text-align: right; 69 | font-size: 12px; 70 | font-family: monospace; 71 | } 72 | 73 | #clientErrorContainer a, #osIncompatibleContainer a { 74 | display: inline-block; 75 | color: rgb(205, 223, 255); 76 | transition: color 200ms ease-in-out; 77 | font-weight: 500; 78 | } 79 | 80 | #clientErrorContainer a:hover, #osIncompatibleContainer a:hover { 81 | color: rgb(90, 225, 255); 82 | } 83 | 84 | #updateMessageContainer { 85 | width: 100%; 86 | margin-bottom: 5px; 87 | background-color: rgb(254, 115, 46); 88 | } 89 | 90 | #updateMessageContainer h1 { 91 | padding-top: 15px; 92 | padding-left: 20px; 93 | padding-bottom: 5px; 94 | color: white; 95 | font-size: 20px; 96 | } 97 | 98 | #updateMessageContainer p { 99 | padding-bottom: 10px; 100 | padding-left: 20px; 101 | padding-right: 20px; 102 | color: rgb(240, 240, 240); 103 | font-size: 16px; 104 | } 105 | 106 | #updateMessageContainer a { 107 | color: rgb(215, 240, 255); 108 | transition: color 200ms ease-in-out; 109 | } 110 | 111 | #updateMessageContainer a:hover { 112 | color: rgb(90, 225, 255); 113 | } 114 | 115 | #updateMessageContainer h6 { 116 | border-top: solid 2px white; 117 | margin-left: 20px; 118 | margin-right: 20px; 119 | margin-top: 5px; 120 | padding-top: 10px; 121 | padding-bottom: 20px; 122 | color: white; 123 | font-weight: 400; 124 | font-size: 14px; 125 | } 126 | 127 | #updateMessageContainer .returnBack { 128 | color: white; 129 | border-color: white; 130 | margin-top: 15px; 131 | margin-right: 15px; 132 | } 133 | 134 | #updateMessageContainer .returnBack:hover { 135 | opacity: 0.7; 136 | } 137 | 138 | #versionWarningContainer { 139 | width: 100%; 140 | margin-bottom: 5px; 141 | background-color: rgb(254, 115, 46); 142 | } 143 | 144 | #versionWarningContainer h1 { 145 | padding-top: 15px; 146 | padding-left: 20px; 147 | padding-bottom: 5px; 148 | color: white; 149 | font-size: 18px; 150 | } 151 | 152 | #versionWarningContainer p { 153 | padding-bottom: 10px; 154 | padding-left: 20px; 155 | padding-right: 20px; 156 | color: rgb(240, 240, 240); 157 | font-size: 14px; 158 | } 159 | 160 | #versionWarningContainer a { 161 | color: rgb(215, 240, 255); 162 | transition: color 200ms ease-in-out; 163 | } 164 | 165 | #versionWarningContainer a:hover { 166 | color: rgb(90, 225, 255); 167 | } 168 | 169 | #versionWarningContainer h6 { 170 | border-top: solid 2px white; 171 | margin-left: 20px; 172 | margin-right: 20px; 173 | padding-top: 10px; 174 | padding-bottom: 20px; 175 | color: white; 176 | font-weight: 400; 177 | font-size: 12px; 178 | } 179 | 180 | #versionWarningContainer .returnBack { 181 | color: white; 182 | border-color: white; 183 | margin-top: 15px; 184 | margin-right: 20px; 185 | } 186 | 187 | #versionWarningContainer .returnBack:hover { 188 | opacity: 0.7; 189 | } 190 | 191 | .pageOutside { 192 | margin-top: 4px; 193 | width: 100%; 194 | height: 500px; 195 | display: flex; 196 | } 197 | 198 | .pageBody { 199 | align-self: center; 200 | width: 92%; 201 | height: 92%; 202 | margin: 2%; 203 | background-color: rgb(50, 50, 50); 204 | border-radius: 5px; 205 | padding-right: 5%; 206 | padding-left: 5%; 207 | padding-top: 3%; 208 | padding-bottom: 4%; 209 | } 210 | 211 | #exclusionsOutside { 212 | display: none; 213 | } 214 | 215 | #inclusionsOutside { 216 | display: none; 217 | } 218 | 219 | #editPresenceOutside { 220 | display: none; 221 | } 222 | 223 | @keyframes flashOrangeAnim { 224 | 50% { 225 | background-color: rgb(124, 87, 47); 226 | } 227 | } 228 | 229 | .flashOrange { 230 | animation: flashOrangeAnim 2s ease-in-out 0s infinite; 231 | } 232 | 233 | div { 234 | vertical-align: top; 235 | border: 0%; 236 | margin: 0%; 237 | } 238 | 239 | .settingsCategory { 240 | font-family: Arial, Helvetica, sans-serif; 241 | font-size: 12px; 242 | font-weight: 700; 243 | margin: 0px; 244 | margin-left: 2px; 245 | margin-top: 14px; 246 | margin-bottom: 4px; 247 | color: rgb(128, 128, 128); 248 | } 249 | 250 | .returnBack { 251 | float: right; 252 | font-family: Arial, Helvetica, sans-serif; 253 | font-size: 12px; 254 | font-weight: 700; 255 | margin: 0px; 256 | margin-top: 6px; 257 | padding-top: 3px; 258 | padding-bottom: 3px; 259 | padding-left: 6px; 260 | padding-right: 6px; 261 | border: 1px; 262 | border-style: solid; 263 | border-radius: 100%; 264 | color: rgb(128, 128, 128); 265 | } 266 | 267 | .returnBack:hover { 268 | color: rgb(207, 207, 207); 269 | cursor: pointer; 270 | } 271 | 272 | .settingsOption { 273 | display: block; 274 | text-decoration: none; 275 | font-family: Arial, Helvetica, sans-serif; 276 | font-size: 16px; 277 | font-weight: 500; 278 | color: rgb(210, 210, 210); 279 | padding-left: 6px; 280 | padding-right: 6px; 281 | padding-top: 15px; 282 | padding-bottom: 15px; 283 | border-bottom: solid; 284 | border-width: 1px; 285 | border-color: rgb(111, 111, 111); 286 | border-top-left-radius: 5px; 287 | border-top-right-radius: 5px; 288 | margin: 0px; 289 | } 290 | 291 | .settingsOption a { 292 | color: rgb(210, 210, 210); 293 | text-decoration: none; 294 | } 295 | 296 | .ieListInput { 297 | font-family: Arial, Helvetica, sans-serif; 298 | font-size: 11px; 299 | font-weight: 500; 300 | color: rgb(210, 210, 210); 301 | margin: 3px; 302 | padding-top: 10px; 303 | padding-bottom: 9px; 304 | padding-left: 7px; 305 | padding-right: 7px; 306 | border-radius: 5px; 307 | border-style: none; 308 | background-color: rgb(70, 70, 70); 309 | width: 270px; 310 | } 311 | 312 | :root { 313 | --videoExclusionsInputButtonBackgroundColor: rgb(60, 60, 60); 314 | --videoExclusionsInputButtonTextColor: rgb(90, 90, 90); 315 | --videoExclusionsInputButtonBackgroundColorHover: rgb(60, 60, 60); 316 | --keywordExclusionsInputButtonBackgroundColor: rgb(60, 60, 60); 317 | --keywordExclusionsInputButtonTextColor: rgb(90, 90, 90); 318 | --keywordExclusionsInputButtonBackgroundColorHover: rgb(60, 60, 60); 319 | 320 | --videoInclusionsInputButtonBackgroundColor: rgb(60, 60, 60); 321 | --videoInclusionsInputButtonTextColor: rgb(90, 90, 90); 322 | --videoInclusionsInputButtonBackgroundColorHover: rgb(60, 60, 60); 323 | --keywordInclusionsInputButtonBackgroundColor: rgb(60, 60, 60); 324 | --keywordInclusionsInputButtonTextColor: rgb(90, 90, 90); 325 | --keywordInclusionsInputButtonBackgroundColorHover: rgb(60, 60, 60); 326 | } 327 | 328 | .ieInputButton { 329 | font-family: Arial, Helvetica, sans-serif; 330 | font-size: 12px; 331 | font-weight: 600; 332 | padding: 9px; 333 | border: solid; 334 | border-style: none; 335 | border-radius: 5px; 336 | cursor: pointer; 337 | } 338 | 339 | #videoExclusionInputButton { 340 | background-color: var(--videoExclusionsInputButtonBackgroundColor); 341 | color: var(--videoExclusionsInputButtonTextColor); 342 | } 343 | 344 | #videoExclusionInputButton:hover { 345 | background-color: var(--videoExclusionsInputButtonBackgroundColorHover); 346 | } 347 | 348 | #keywordExclusionInputButton { 349 | background-color: var(--keywordExclusionsInputButtonBackgroundColor); 350 | color: var(--keywordExclusionsInputButtonTextColor); 351 | } 352 | 353 | #keywordExclusionInputButton:hover { 354 | background-color: var(--keywordExclusionsInputButtonBackgroundColorHover); 355 | } 356 | 357 | #videoInclusionInputButton { 358 | background-color: var(--videoInclusionsInputButtonBackgroundColor); 359 | color: var(--videoInclusionsInputButtonTextColor); 360 | } 361 | 362 | #videoInclusionInputButton:hover { 363 | background-color: var(--videoInclusionsInputButtonBackgroundColorHover); 364 | } 365 | 366 | #keywordInclusionInputButton { 367 | background-color: var(--keywordInclusionsInputButtonBackgroundColor); 368 | color: var(--keywordInclusionsInputButtonTextColor); 369 | } 370 | 371 | #keywordInclusionInputButton:hover { 372 | background-color: var(--keywordInclusionsInputButtonBackgroundColorHover); 373 | } 374 | 375 | .separator { 376 | margin: 10px; 377 | } 378 | 379 | .ieListBody { 380 | /* ie is abbreviation for includeExclude */ 381 | margin-top: 8px; 382 | margin-bottom: 8px; 383 | margin-left: 3px; 384 | margin-right: 3px; 385 | background-color: rgb(56, 56, 56); 386 | border-radius: 5px; 387 | padding: 0px; 388 | height: 126px; 389 | overflow: hidden; 390 | overflow-y: scroll; 391 | } 392 | 393 | .ieList { 394 | color: rgb(210, 210, 210); 395 | list-style-type: none; 396 | margin-bottom: 8px; 397 | padding-top: 0px; 398 | padding-left: 8px; 399 | padding-right: 8px; 400 | font-size: 14px; 401 | } 402 | 403 | li { 404 | background-color: rgb(50, 45, 50); 405 | border-radius: 3px; 406 | padding: 8px; 407 | width: 100%; 408 | max-width: 300px; 409 | overflow-wrap: break-word; 410 | margin-top: 8px; 411 | font-size: 14px; 412 | } 413 | 414 | div::-webkit-scrollbar { 415 | width: 5px; 416 | } 417 | 418 | div::-webkit-scrollbar-track { 419 | background: transparent; 420 | } 421 | 422 | div::-webkit-scrollbar-thumb { 423 | background: rgb(210, 210, 210); 424 | border-radius: 4px; 425 | } 426 | 427 | div::-webkit-scrollbar-thumb:hover { 428 | background: rgb(177, 177, 177); 429 | } 430 | 431 | .removeIeButton { 432 | position: relative; 433 | font-size: 10px; 434 | color: rgb(128, 128, 128); 435 | margin-left: 1px; 436 | margin-top: 4px; 437 | } 438 | 439 | .urlDetails { 440 | position: relative; 441 | font-size: 10px; 442 | color: rgb(170, 170, 170); 443 | margin-left: 1px; 444 | margin-top: 4px; 445 | } 446 | 447 | .removeIeButton:hover { 448 | cursor: pointer; 449 | color: red; 450 | font-weight: 500; 451 | } 452 | 453 | .switchStatus { 454 | float: right; 455 | position: relative; 456 | margin: 6px; 457 | margin-right: 8px; 458 | font-size: 10px; 459 | font-weight: bold; 460 | } 461 | 462 | .triangle { 463 | width: 0; 464 | height: 0; 465 | border-top: 5px solid transparent; 466 | border-left: 10px solid white; 467 | border-bottom: 5px solid transparent; 468 | } 469 | 470 | .subpageButton { 471 | float: right; 472 | position: relative; 473 | margin: 6px; 474 | margin-right: 8px; 475 | font-size: 10px; 476 | font-weight: bold; 477 | } 478 | 479 | .subpageButtonBody:hover { 480 | background-color: rgb(40, 40, 40); 481 | cursor: pointer; 482 | } 483 | 484 | .switch { 485 | float: right; 486 | position: relative; 487 | vertical-align: middle; 488 | width: 45px; 489 | height: 22px; 490 | margin: 0px; 491 | } 492 | 493 | .switch input { 494 | opacity: 0; 495 | width: 0; 496 | height: 0; 497 | } 498 | 499 | .slider { 500 | position: absolute; 501 | cursor: pointer; 502 | top: 0; 503 | left: 0; 504 | right: 0; 505 | bottom: 0; 506 | background-color: rgb(149, 149, 149); 507 | transition: 0.2s; 508 | transition: 0.2s; 509 | } 510 | 511 | .slider::before { 512 | position: absolute; 513 | content: ""; 514 | height: 16px; 515 | width: 16px; 516 | left: 3px; 517 | bottom: 3px; 518 | background-color: white; 519 | transition: 0.2s; 520 | transition: 0.2s; 521 | } 522 | 523 | input:checked+.slider { 524 | background-color: #2ab045; 525 | } 526 | 527 | input:focus+.slider { 528 | box-shadow: 0 0 1px #2ab045; 529 | } 530 | 531 | input:checked+.slider::before { 532 | transform: translateX(22px); 533 | } 534 | 535 | .slider.round { 536 | border-radius: 22px; 537 | } 538 | 539 | .slider.round::before { 540 | border-radius: 50%; 541 | } -------------------------------------------------------------------------------- /Extension/popup.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | YouTubeDiscordPresence Settings 12 | 13 | 14 | 15 | 16 | 17 |
18 | YouTubeDiscordPresence Settings 19 |
20 | 29 | 37 | 52 |
53 |
54 |

HOME

55 |

Enable extension 56 | 60 | OFF 61 |

62 |

Enable on startup 63 | 67 | OFF 68 |

69 |

ADVANCED

70 |

Enable on this tab 71 | 75 | OFF 76 |

77 |

Edit presence details 78 | 79 |

80 |

Add videos to exclude 81 | 82 |

83 |

Add videos to include 84 | 85 |

86 |

TROUBLESHOOTING AND FEATURE REQUESTS

87 | 89 | Visit the GitHub repository 90 | 91 | 92 |
93 |
94 |
95 |
96 | 97 |

EXCLUSIONS

98 |

Exclude certain videos 99 | 103 | OFF 104 |

105 |
106 |
107 | 109 | ADD 110 |
111 |
112 |
    113 | 121 |
122 |
123 |
124 | 125 | ADD 126 |
127 |
128 |
    129 |
    130 |
    131 |
    132 |
    133 |
    134 | 135 |

    INCLUSIONS

    136 |

    Only display certain videos 137 | 141 | OFF 142 |

    143 |
    144 |
    145 | 147 | ADD 148 |
    149 |
    150 |
      151 | 159 |
    160 |
    161 |
    162 | 163 | ADD 164 |
    165 |
    166 |
      167 |
      168 |
      169 |
      170 |
      171 |
      172 | 173 |

      APPLICATION SPECIFIC TOGGLES

      174 |

      Enable on YouTube 175 | 179 | OFF 180 |

      181 |

      Enable on YouTube Music 182 | 186 | OFF 187 |

      188 |

      MISCELLANEOUS

      189 |

      Enable video button 190 | 194 | OFF 195 |

      196 |

      Enable channel button 197 | 201 | OFF 202 |

      203 |

      Enable corner icon 204 | 205 | 209 | OFF 210 |

      211 |

      Add "by" before author 212 | 216 | OFF 217 |

      218 |

      Use album cover for YT Music 219 | 223 | OFF 224 |

      225 |

      Use video thumbnail as large icon 226 | 230 | OFF 231 |

      232 |
      233 |
      234 | 235 | 236 | 237 | 238 | 242 | -------------------------------------------------------------------------------- /Extension/background.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022–Present Michael Ren 3 | Licensing and distribution info can be found at the GitHub repository 4 | https://github.com/XFG16/YouTubeDiscordPresence 5 | */ 6 | 7 | // MAIN VARIABLE INITIALIZATION 8 | 9 | const LOGGING = true; 10 | 11 | const NMF = Object.freeze({ // NMF = NATIVE_MESSAGE_FORMAT (FOR HANDLING BY YTDPwin.exe) 12 | TITLE: ":TITLE001:", 13 | AUTHOR: ":AUTHOR002:", 14 | TIME_LEFT: ":TIMELEFT003:", 15 | END: ":END004:", 16 | IDLE: "#*IDLE*#" 17 | }); 18 | 19 | const NORMAL_MESSAGE_DELAY = 1000; 20 | const LIVESTREAM_TIME_ID = -1; 21 | const UPDATE_PRESENCE_MESSAGE = "UPDATE_PRESENCE_DATA"; 22 | const REQUIRED_NATIVE_VERSION = "1.4.2"; 23 | 24 | let nativeVersionStatus = -2; 25 | let currentMessage = new Object(); 26 | let previousMessage = new Object(); 27 | let lastUpdated = 9007199254740991; 28 | let isIdle = true; 29 | 30 | let settings = { 31 | enabled: true, 32 | enableOnStartup: true, 33 | tabEnabledList: new Object(), 34 | 35 | enableExclusions: false, 36 | videoExclusionsList: new Array(), 37 | keywordExclusionsList: new Array(), 38 | 39 | enableInclusions: false, 40 | videoInclusionsList: new Array(), 41 | keywordInclusionsList: new Array(), 42 | 43 | enableYouTube: true, 44 | enableYouTubeMusic: true, 45 | enableVideoButton: true, 46 | enableChannelButton: true, 47 | enablePlayingIcon: true, 48 | addByAuthor: true, 49 | useAlbumThumbnail: true, 50 | useThumbnailIcon: false 51 | } 52 | 53 | // MUST RUN EVERY TIME BACKGROUND.JS STARTS - INITIALIZES KEYS JUST IN CASE THEY WEREN'T INITIALIZED BEFORE 54 | // isNativeConnected is saved separately 55 | 56 | for (const key of Object.keys(settings)) { 57 | chrome.storage.sync.get(key, function (result) { 58 | if (result[key] == undefined) { 59 | saveStorageKey(key, settings[key]); 60 | } 61 | else { 62 | settings[key] = result[key]; 63 | } 64 | }); 65 | } 66 | 67 | // VERSION COMPARER (REFERENCE: https://gist.github.com/TheDistantSea/8021359) 68 | 69 | function versionCompare(v1, v2, options) { // CHECKS IF V1 IS GREATER THAN (1), EQUAL (0), OR LESS THAN V2 (-1) 70 | var lexicographical = options && options.lexicographical, 71 | zeroExtend = options && options.zeroExtend, 72 | v1parts = v1.split('.'), 73 | v2parts = v2.split('.'); 74 | 75 | function isValidPart(x) { 76 | return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); 77 | } 78 | 79 | if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 80 | return NaN; 81 | } 82 | 83 | if (zeroExtend) { 84 | while (v1parts.length < v2parts.length) v1parts.push("0"); 85 | while (v2parts.length < v1parts.length) v2parts.push("0"); 86 | } 87 | 88 | if (!lexicographical) { 89 | v1parts = v1parts.map(Number); 90 | v2parts = v2parts.map(Number); 91 | } 92 | 93 | for (var i = 0; i < v1parts.length; ++i) { 94 | if (v2parts.length == i) { 95 | return 1; 96 | } 97 | 98 | if (v1parts[i] == v2parts[i]) { 99 | continue; 100 | } 101 | else if (v1parts[i] > v2parts[i]) { 102 | return 1; 103 | } 104 | else { 105 | return -1; 106 | } 107 | } 108 | 109 | if (v1parts.length != v2parts.length) { 110 | return -1; 111 | } 112 | 113 | return 0; 114 | } 115 | 116 | // NODE.JS APPLICATION HANDLER 117 | 118 | const handleNativeMessage = (message) => { 119 | if (LOGGING && message.data) { 120 | console.log(`Received from application:\n ${message.data}`); 121 | } 122 | else if (message.nativeVersion) { 123 | nativeVersionStatus = versionCompare(message.nativeVersion, REQUIRED_NATIVE_VERSION); 124 | saveStorageKey("nativeVersionStatus", nativeVersionStatus); 125 | } 126 | else if (LOGGING) { 127 | console.log(`Unknown application message:\n ${message}`); 128 | } 129 | } 130 | 131 | // CONNECTING TO DESKTOP APP 132 | 133 | let isNativeConnected = false; 134 | let nativePort = {}; 135 | 136 | function assertNativeExistence(callback = null) { 137 | if (!isNativeConnected) { 138 | if (nativePort.disconnect) nativePort.disconnect(); 139 | nativePort = chrome.runtime.connectNative("com.ytdp.discord.presence"); 140 | 141 | isNativeConnected = true; 142 | nativePort.onDisconnect.addListener(() => { 143 | if (chrome.runtime.lastError) { 144 | isNativeConnected = false; 145 | saveStorageKey("isNativeConnected", false); 146 | console.log("The YouTubeDiscordPresence desktop component was not properly installed.\nVisit https://github.com/XFG16/YouTubeDiscordPresence#installation"); 147 | } 148 | }); 149 | 150 | setTimeout(() => { 151 | if (isNativeConnected) { 152 | saveStorageKey("isNativeConnected", true); 153 | nativePort.onMessage.addListener(handleNativeMessage); 154 | if (callback) callback(); 155 | } 156 | }, 1000); 157 | } 158 | else if (callback) { 159 | callback(); 160 | } 161 | } 162 | 163 | assertNativeExistence(() => { 164 | nativePort.postMessage({ getNativeVersion: true }); 165 | setTimeout(() => { 166 | if (nativeVersionStatus == -2) saveStorageKey("nativeVersionStatus", -1); 167 | }, 1000); 168 | }); 169 | setInterval(() => { 170 | assertNativeExistence(); 171 | }, 3000); 172 | 173 | // STORAGE SAVING HANDLER 174 | 175 | function saveStorageKey(key, value) { 176 | let saveObject = new Object(); 177 | saveObject[key] = value; 178 | chrome.storage.sync.set(saveObject); 179 | } 180 | 181 | // PARSE YOUTUBE URLS (https://stackoverflow.com/questions/3452546/how-do-i-get-the-youtube-video-id-from-a-url) 182 | 183 | function getVideoId(url) { 184 | let regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; 185 | let match = url.match(regExp); 186 | return (match && match[7].length == 11) ? match[7] : null; 187 | } 188 | 189 | // CHECK WHETHER OR NOT VIDEO/CHANNEL TITLE OR VIDEO ID IS EXCLUDED 190 | 191 | function isExcluded(title, author, videoUrl) { 192 | if (settings.enableExclusions == false) { 193 | return false; 194 | } 195 | for (let i = 0; i < settings.videoExclusionsList.length; ++i) { 196 | excludedVideoId = getVideoId(settings.videoExclusionsList[i]); 197 | if (excludedVideoId && getVideoId(videoUrl) == excludedVideoId) { 198 | return true; 199 | } 200 | if (title == settings.videoExclusionsList[i] || author == settings.videoExclusionsList[i]) { 201 | return true; 202 | } 203 | } 204 | for (let i = 0; i < settings.keywordExclusionsList.length; ++i) { 205 | let keyword = settings.keywordExclusionsList[i].toLowerCase(); 206 | if (title.toLowerCase().includes(keyword) || author.toLowerCase().includes(keyword)) { 207 | return true; 208 | } 209 | } 210 | return false; 211 | } 212 | 213 | // CHECK WHETHER OR NOT VIDEO/CHANNEL TITLE OR VIDEO ID IS INCLUDED 214 | 215 | function isIncluded(title, author, videoId) { 216 | if (settings.enableInclusions == false) { 217 | return true; 218 | } 219 | for (let i = 0; i < settings.videoInclusionsList.length; ++i) { 220 | includedVideoId = getVideoId(settings.videoInclusionsList[i]); 221 | if (includedVideoId && videoId == includedVideoId) { 222 | return true; 223 | } 224 | if (title == settings.videoInclusionsList[i] || author == settings.videoInclusionsList[i]) { 225 | return true; 226 | } 227 | } 228 | for (let i = 0; i < settings.keywordInclusionsList.length; ++i) { 229 | let keyword = settings.keywordInclusionsList[i].toLowerCase(); 230 | if (title.toLowerCase().includes(keyword) || author.toLowerCase().includes(keyword)) { 231 | return true; 232 | } 233 | } 234 | return false; 235 | } 236 | 237 | // STORAGE INITIALIZER WHEN CHROME IS OPENED 238 | 239 | chrome.runtime.onStartup.addListener(function () { 240 | chrome.storage.sync.get("enableOnStartup", function (result) { 241 | saveStorageKey("enableOnStartup", result.enableOnStartup == undefined || result.enableOnStartup == true); 242 | saveStorageKey("enabled", result.enableOnStartup == undefined || result.enableOnStartup == true); 243 | settings.enabled = (result.enableOnStartup == undefined || result.enableOnStartup == true); 244 | }); 245 | saveStorageKey("tabEnabledList", new Object()); 246 | settings.tabEnabledList = new Object(); 247 | }); 248 | 249 | // REMOVE ENABLEONTHISTAB WHEN TAB IS CLOSED 250 | 251 | chrome.tabs.onRemoved.addListener(function (tab) { 252 | chrome.storage.sync.get("tabEnabledList", function (result) { 253 | let newTabEnabledList = result.tabEnabledList; 254 | if (tab in newTabEnabledList) { 255 | delete newTabEnabledList[tab]; 256 | } 257 | saveStorageKey("tabEnabledList", newTabEnabledList); 258 | }); 259 | }); 260 | 261 | // DETECT CHANGE IN ENABLED SETTING 262 | 263 | chrome.storage.onChanged.addListener(function (changes, namespace) { 264 | for (let [key, { oldValue, newValue }] of Object.entries(changes)) { 265 | if (LOGGING) { 266 | console.log("the key {", key, "} has been changed from", oldValue, "to", newValue) 267 | } 268 | settings[key] = newValue; 269 | } 270 | }); 271 | 272 | // LISTENER FOR DATA FROM CONTENT_LOADER.JS 273 | // SELECTION ON WHICH TAB TO DISPLAY IS BASED ON WHICH ONE IS LOADED FIRST BY SETTING CURRENTMESSAGE.SCRIPTID TO NULL 274 | 275 | function isApplicationTypeEnabled(applicationType) { 276 | if (applicationType == "youtube") { 277 | return settings.enableYouTube; 278 | } 279 | else { 280 | return settings.enableYouTubeMusic; 281 | } 282 | } 283 | 284 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 285 | if (message.messageType == UPDATE_PRESENCE_MESSAGE && (sender.tab.id == currentMessage.scriptId || currentMessage.scriptId == null) && !isExcluded(message.title, message.author, message.videoId) && isIncluded(message.title, message.author, message.videoId) && isApplicationTypeEnabled(message.applicationType)) { 286 | if (!(sender.tab.id in settings.tabEnabledList)) { 287 | settings.tabEnabledList[sender.tab.id] = true; 288 | } 289 | if (settings.tabEnabledList[sender.tab.id]) { 290 | currentMessage.scriptId = sender.tab.id; 291 | currentMessage.title = message.title; 292 | currentMessage.author = message.author; 293 | currentMessage.timeLeft = message.timeLeft; 294 | currentMessage.duration = message.duration; 295 | currentMessage.videoId = message.videoId; 296 | currentMessage.videoUrl = "https://youtube.com/watch?v=" + message.videoId; 297 | currentMessage.channelUrl = message.channelUrl; 298 | currentMessage.applicationType = message.applicationType; 299 | currentMessage.thumbnailUrl = message.thumbnailUrl; 300 | lastUpdated = new Date().getTime(); 301 | sendResponse(null); 302 | } 303 | } 304 | return true; 305 | }); 306 | 307 | // NATIVE MESSAGING HANDLER 308 | 309 | const IDLE_DATA_OBJECT = { 310 | cppData: NMF.TITLE + NMF.IDLE + NMF.AUTHOR + NMF.IDLE + NMF.TIME_LEFT + NMF.IDLE + NMF.END, 311 | jsTitle: NMF.IDLE, 312 | presenceData: NMF.IDLE 313 | }; 314 | 315 | function idleCallback() { 316 | if (LOGGING) console.log("Idle data sent:\n #*IDLE*#"); 317 | nativePort.postMessage(IDLE_DATA_OBJECT); 318 | 319 | currentMessage.scriptId = null; 320 | previousMessage = {}; 321 | isIdle = true; 322 | } 323 | 324 | // FOR 1.5.2 OR ABOVE, ALL PRESENCE DATA WILL BE HANDLED AND SENT IN THE EXTENSION UNDER presenceData. OTHER OBJECT KEY PAIRS ARE FOR BACKWARDS COMPATIBILITY 325 | 326 | function generatePresenceData() { 327 | let stateData = ""; 328 | if (currentMessage.timeLeft != LIVESTREAM_TIME_ID) { 329 | stateData = settings.addByAuthor ? `by ${currentMessage.author}` : currentMessage.author; 330 | } 331 | else { 332 | stateData = settings.addByAuthor ? `[LIVE] on ${currentMessage.author}` : currentMessage.author; 333 | } 334 | 335 | let assetsData = { 336 | large_image: "youtube3", 337 | large_text: currentMessage.title.substring(0, 128) 338 | }; 339 | if (currentMessage.applicationType == "youtubeMusic") { 340 | if (currentMessage.thumbnailUrl.startsWith("https://lh3.googleusercontent.com/") && settings.useAlbumThumbnail) { 341 | assetsData.large_image = currentMessage.thumbnailUrl; 342 | } 343 | else if (settings.useThumbnailIcon && !currentMessage.thumbnailUrl.startsWith("https://lh3.googleusercontent.com/")) { 344 | assetsData.large_image = currentMessage.thumbnailUrl; 345 | } 346 | else { 347 | assetsData.large_image = "youtube-music"; 348 | } 349 | } 350 | else { 351 | if (settings.useThumbnailIcon) { 352 | assetsData.large_image = currentMessage.thumbnailUrl; 353 | } 354 | else if (currentMessage.timeLeft == LIVESTREAM_TIME_ID) { 355 | assetsData.large_image = "youtubelive1"; 356 | } 357 | } 358 | 359 | if (settings.enablePlayingIcon) { 360 | assetsData.small_image = "playing-icon-6"; 361 | assetsData.small_text = "YouTubeDiscordPresence on GitHub"; 362 | } 363 | 364 | let timeStampsData = {}; 365 | if (currentMessage.timeLeft != LIVESTREAM_TIME_ID) { 366 | // timeStampsData.end = Date.now() + (currentMessage.timeLeft * 1000); 367 | 368 | // Changed to handle Discord UI/UX updates 369 | // For more info, visit https://github.com/XFG16/YouTubeDiscordPresence#announcements 370 | timeStampsData.start = Date.now() - ((currentMessage.duration - currentMessage.timeLeft) * 1000); 371 | } 372 | else { 373 | timeStampsData.start = Date.now(); 374 | } 375 | 376 | let buttonsData = []; 377 | if (settings.enableVideoButton && currentMessage.videoUrl) { 378 | if (currentMessage.applicationType == "youtubeMusic") { 379 | buttonsData.push({ 380 | label: "Listen Along", 381 | url: currentMessage.videoUrl 382 | }); 383 | } 384 | else if (currentMessage.timeLeft == LIVESTREAM_TIME_ID) { 385 | buttonsData.push({ 386 | label: "Watch Livestream", 387 | url: currentMessage.videoUrl 388 | }); 389 | } 390 | else { 391 | buttonsData.push({ 392 | label: "Watch Video", 393 | url: currentMessage.videoUrl 394 | }); 395 | } 396 | } 397 | if (settings.enableChannelButton && currentMessage.channelUrl && !currentMessage.channelUrl.endsWith("undefined")) { 398 | buttonsData.push({ 399 | label: "View Channel", 400 | url: currentMessage.channelUrl 401 | }); 402 | } 403 | 404 | let presenceData = { 405 | details: currentMessage.title.substring(0, 128), 406 | state: stateData.substring(0, 128), 407 | assets: assetsData, 408 | timestamps: timeStampsData, 409 | }; 410 | if (buttonsData.length > 0) { 411 | presenceData.buttons = buttonsData; 412 | } 413 | 414 | return presenceData; 415 | } 416 | 417 | function updateCallback() { 418 | let dataObject = { 419 | cppData: NMF.TITLE + currentMessage.title + NMF.AUTHOR + currentMessage.author + NMF.TIME_LEFT + Math.round(currentMessage.timeLeft) + NMF.END, 420 | jsTitle: currentMessage.title, 421 | jsAuthor: currentMessage.author, 422 | jsTimeLeft: currentMessage.timeLeft, 423 | jsVideoUrl: currentMessage.videoUrl, 424 | jsChannelUrl: currentMessage.channelUrl, 425 | jsPresenceSettings: { 426 | enableVideoButton: settings.enableVideoButton, 427 | enableChannelButton: settings.enableChannelButton, 428 | enablePlayingIcon: settings.enablePlayingIcon, 429 | addByAuthor: settings.addByAuthor 430 | }, 431 | jsApplicationType: currentMessage.applicationType, 432 | presenceData: generatePresenceData() 433 | 434 | }; 435 | 436 | if (LOGGING) console.log("Presence data:", dataObject); 437 | nativePort.postMessage(dataObject); 438 | } 439 | 440 | let pipeInterval = setInterval(function () { 441 | let inclusionExclusionStatus = false; 442 | if (!(Object.keys(currentMessage).length == 0) && (isExcluded(currentMessage.title, currentMessage.author, currentMessage.videoUrl) || !isIncluded(currentMessage.title, currentMessage.author, currentMessage.videoId))) { 443 | inclusionExclusionStatus = true; 444 | } 445 | let delaySinceUpdate = new Date().getTime() - lastUpdated; 446 | if (!settings.enabled || !(currentMessage.scriptId in settings.tabEnabledList) || delaySinceUpdate >= 3 * NORMAL_MESSAGE_DELAY || inclusionExclusionStatus) { 447 | if (!isIdle) { 448 | assertNativeExistence(idleCallback); 449 | } 450 | return; 451 | } 452 | if (delaySinceUpdate >= 2 * NORMAL_MESSAGE_DELAY) { 453 | currentMessage.scriptId = null; 454 | } 455 | 456 | let skipMessage = false; 457 | if (previousMessage.timeLeft >= currentMessage.timeLeft && ((1000 * (previousMessage.timeLeft - currentMessage.timeLeft) < 2 * NORMAL_MESSAGE_DELAY) || (previousMessage.timeLeft == LIVESTREAM_TIME_ID && currentMessage.timeLeft != LIVESTREAM_TIME_ID))) { 458 | skipMessage = true; 459 | } 460 | if (!(previousMessage.title == currentMessage.title && previousMessage.author == currentMessage.author && previousMessage.thumbnailUrl == currentMessage.thumbnailUrl && skipMessage)) { 461 | if (nativeVersionStatus < 0) { 462 | function getNativeVersion() { 463 | nativePort.postMessage({ getNativeVersion: true }); 464 | } 465 | assertNativeExistence(getNativeVersion); 466 | } 467 | assertNativeExistence(updateCallback); 468 | } 469 | 470 | previousMessage.title = currentMessage.title; 471 | previousMessage.author = currentMessage.author; 472 | previousMessage.timeLeft = currentMessage.timeLeft; 473 | previousMessage.thumbnailUrl = currentMessage.thumbnailUrl; 474 | isIdle = false; 475 | }, NORMAL_MESSAGE_DELAY); 476 | 477 | // EXTENSION UPDATE HANDLER 478 | 479 | chrome.runtime.onUpdateAvailable.addListener(function (details) { 480 | console.log(`YTDP IS updating to ${details.version}`); 481 | chrome.runtime.reload(); 482 | }); 483 | 484 | // OPEN INSTALLATION PAGE ON CHROME EXTENSION ADDED 485 | 486 | chrome.runtime.onInstalled.addListener(function (install) { 487 | if (install.reason == chrome.runtime.OnInstalledReason.INSTALL) { 488 | chrome.tabs.create({ url: "https://github.com/XFG16/YouTubeDiscordPresence/tree/main#installation" }, function (tab) { 489 | console.log("Redirected user to installation page at\nhttps://github.com/XFG16/YouTubeDiscordPresence/tree/main#installation"); 490 | }); 491 | saveStorageKey("flashEditPresence", true); 492 | } 493 | }); -------------------------------------------------------------------------------- /Host/Discord/API/types.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_CRT_SECURE_NO_WARNINGS) 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "types.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace discord { 11 | 12 | void User::SetId(UserId id) 13 | { 14 | internal_.id = id; 15 | } 16 | 17 | UserId User::GetId() const 18 | { 19 | return internal_.id; 20 | } 21 | 22 | void User::SetUsername(char const* username) 23 | { 24 | strncpy(internal_.username, username, 256); 25 | internal_.username[256 - 1] = '\0'; 26 | } 27 | 28 | char const* User::GetUsername() const 29 | { 30 | return internal_.username; 31 | } 32 | 33 | void User::SetDiscriminator(char const* discriminator) 34 | { 35 | strncpy(internal_.discriminator, discriminator, 8); 36 | internal_.discriminator[8 - 1] = '\0'; 37 | } 38 | 39 | char const* User::GetDiscriminator() const 40 | { 41 | return internal_.discriminator; 42 | } 43 | 44 | void User::SetAvatar(char const* avatar) 45 | { 46 | strncpy(internal_.avatar, avatar, 128); 47 | internal_.avatar[128 - 1] = '\0'; 48 | } 49 | 50 | char const* User::GetAvatar() const 51 | { 52 | return internal_.avatar; 53 | } 54 | 55 | void User::SetBot(bool bot) 56 | { 57 | internal_.bot = bot; 58 | } 59 | 60 | bool User::GetBot() const 61 | { 62 | return internal_.bot != 0; 63 | } 64 | 65 | void OAuth2Token::SetAccessToken(char const* accessToken) 66 | { 67 | strncpy(internal_.access_token, accessToken, 128); 68 | internal_.access_token[128 - 1] = '\0'; 69 | } 70 | 71 | char const* OAuth2Token::GetAccessToken() const 72 | { 73 | return internal_.access_token; 74 | } 75 | 76 | void OAuth2Token::SetScopes(char const* scopes) 77 | { 78 | strncpy(internal_.scopes, scopes, 1024); 79 | internal_.scopes[1024 - 1] = '\0'; 80 | } 81 | 82 | char const* OAuth2Token::GetScopes() const 83 | { 84 | return internal_.scopes; 85 | } 86 | 87 | void OAuth2Token::SetExpires(Timestamp expires) 88 | { 89 | internal_.expires = expires; 90 | } 91 | 92 | Timestamp OAuth2Token::GetExpires() const 93 | { 94 | return internal_.expires; 95 | } 96 | 97 | void ImageHandle::SetType(ImageType type) 98 | { 99 | internal_.type = static_cast(type); 100 | } 101 | 102 | ImageType ImageHandle::GetType() const 103 | { 104 | return static_cast(internal_.type); 105 | } 106 | 107 | void ImageHandle::SetId(std::int64_t id) 108 | { 109 | internal_.id = id; 110 | } 111 | 112 | std::int64_t ImageHandle::GetId() const 113 | { 114 | return internal_.id; 115 | } 116 | 117 | void ImageHandle::SetSize(std::uint32_t size) 118 | { 119 | internal_.size = size; 120 | } 121 | 122 | std::uint32_t ImageHandle::GetSize() const 123 | { 124 | return internal_.size; 125 | } 126 | 127 | void ImageDimensions::SetWidth(std::uint32_t width) 128 | { 129 | internal_.width = width; 130 | } 131 | 132 | std::uint32_t ImageDimensions::GetWidth() const 133 | { 134 | return internal_.width; 135 | } 136 | 137 | void ImageDimensions::SetHeight(std::uint32_t height) 138 | { 139 | internal_.height = height; 140 | } 141 | 142 | std::uint32_t ImageDimensions::GetHeight() const 143 | { 144 | return internal_.height; 145 | } 146 | 147 | void ActivityTimestamps::SetStart(Timestamp start) 148 | { 149 | internal_.start = start; 150 | } 151 | 152 | Timestamp ActivityTimestamps::GetStart() const 153 | { 154 | return internal_.start; 155 | } 156 | 157 | void ActivityTimestamps::SetEnd(Timestamp end) 158 | { 159 | internal_.end = end; 160 | } 161 | 162 | Timestamp ActivityTimestamps::GetEnd() const 163 | { 164 | return internal_.end; 165 | } 166 | 167 | void ActivityAssets::SetLargeImage(char const* largeImage) 168 | { 169 | strncpy(internal_.large_image, largeImage, 128); 170 | internal_.large_image[128 - 1] = '\0'; 171 | } 172 | 173 | char const* ActivityAssets::GetLargeImage() const 174 | { 175 | return internal_.large_image; 176 | } 177 | 178 | void ActivityAssets::SetLargeText(char const* largeText) 179 | { 180 | strncpy(internal_.large_text, largeText, 128); 181 | internal_.large_text[128 - 1] = '\0'; 182 | } 183 | 184 | char const* ActivityAssets::GetLargeText() const 185 | { 186 | return internal_.large_text; 187 | } 188 | 189 | void ActivityAssets::SetSmallImage(char const* smallImage) 190 | { 191 | strncpy(internal_.small_image, smallImage, 128); 192 | internal_.small_image[128 - 1] = '\0'; 193 | } 194 | 195 | char const* ActivityAssets::GetSmallImage() const 196 | { 197 | return internal_.small_image; 198 | } 199 | 200 | void ActivityAssets::SetSmallText(char const* smallText) 201 | { 202 | strncpy(internal_.small_text, smallText, 128); 203 | internal_.small_text[128 - 1] = '\0'; 204 | } 205 | 206 | char const* ActivityAssets::GetSmallText() const 207 | { 208 | return internal_.small_text; 209 | } 210 | 211 | void PartySize::SetCurrentSize(std::int32_t currentSize) 212 | { 213 | internal_.current_size = currentSize; 214 | } 215 | 216 | std::int32_t PartySize::GetCurrentSize() const 217 | { 218 | return internal_.current_size; 219 | } 220 | 221 | void PartySize::SetMaxSize(std::int32_t maxSize) 222 | { 223 | internal_.max_size = maxSize; 224 | } 225 | 226 | std::int32_t PartySize::GetMaxSize() const 227 | { 228 | return internal_.max_size; 229 | } 230 | 231 | void ActivityParty::SetId(char const* id) 232 | { 233 | strncpy(internal_.id, id, 128); 234 | internal_.id[128 - 1] = '\0'; 235 | } 236 | 237 | char const* ActivityParty::GetId() const 238 | { 239 | return internal_.id; 240 | } 241 | 242 | PartySize& ActivityParty::GetSize() 243 | { 244 | return reinterpret_cast(internal_.size); 245 | } 246 | 247 | PartySize const& ActivityParty::GetSize() const 248 | { 249 | return reinterpret_cast(internal_.size); 250 | } 251 | 252 | void ActivitySecrets::SetMatch(char const* match) 253 | { 254 | strncpy(internal_.match, match, 128); 255 | internal_.match[128 - 1] = '\0'; 256 | } 257 | 258 | char const* ActivitySecrets::GetMatch() const 259 | { 260 | return internal_.match; 261 | } 262 | 263 | void ActivitySecrets::SetJoin(char const* join) 264 | { 265 | strncpy(internal_.join, join, 128); 266 | internal_.join[128 - 1] = '\0'; 267 | } 268 | 269 | char const* ActivitySecrets::GetJoin() const 270 | { 271 | return internal_.join; 272 | } 273 | 274 | void ActivitySecrets::SetSpectate(char const* spectate) 275 | { 276 | strncpy(internal_.spectate, spectate, 128); 277 | internal_.spectate[128 - 1] = '\0'; 278 | } 279 | 280 | char const* ActivitySecrets::GetSpectate() const 281 | { 282 | return internal_.spectate; 283 | } 284 | 285 | void Activity::SetType(ActivityType type) 286 | { 287 | internal_.type = static_cast(type); 288 | } 289 | 290 | ActivityType Activity::GetType() const 291 | { 292 | return static_cast(internal_.type); 293 | } 294 | 295 | void Activity::SetApplicationId(std::int64_t applicationId) 296 | { 297 | internal_.application_id = applicationId; 298 | } 299 | 300 | std::int64_t Activity::GetApplicationId() const 301 | { 302 | return internal_.application_id; 303 | } 304 | 305 | void Activity::SetName(char const* name) 306 | { 307 | strncpy(internal_.name, name, 128); 308 | internal_.name[128 - 1] = '\0'; 309 | } 310 | 311 | char const* Activity::GetName() const 312 | { 313 | return internal_.name; 314 | } 315 | 316 | void Activity::SetState(char const* state) 317 | { 318 | strncpy(internal_.state, state, 128); 319 | internal_.state[128 - 1] = '\0'; 320 | } 321 | 322 | char const* Activity::GetState() const 323 | { 324 | return internal_.state; 325 | } 326 | 327 | void Activity::SetDetails(char const* details) 328 | { 329 | strncpy(internal_.details, details, 128); 330 | internal_.details[128 - 1] = '\0'; 331 | } 332 | 333 | char const* Activity::GetDetails() const 334 | { 335 | return internal_.details; 336 | } 337 | 338 | ActivityTimestamps& Activity::GetTimestamps() 339 | { 340 | return reinterpret_cast(internal_.timestamps); 341 | } 342 | 343 | ActivityTimestamps const& Activity::GetTimestamps() const 344 | { 345 | return reinterpret_cast(internal_.timestamps); 346 | } 347 | 348 | ActivityAssets& Activity::GetAssets() 349 | { 350 | return reinterpret_cast(internal_.assets); 351 | } 352 | 353 | ActivityAssets const& Activity::GetAssets() const 354 | { 355 | return reinterpret_cast(internal_.assets); 356 | } 357 | 358 | ActivityParty& Activity::GetParty() 359 | { 360 | return reinterpret_cast(internal_.party); 361 | } 362 | 363 | ActivityParty const& Activity::GetParty() const 364 | { 365 | return reinterpret_cast(internal_.party); 366 | } 367 | 368 | ActivitySecrets& Activity::GetSecrets() 369 | { 370 | return reinterpret_cast(internal_.secrets); 371 | } 372 | 373 | ActivitySecrets const& Activity::GetSecrets() const 374 | { 375 | return reinterpret_cast(internal_.secrets); 376 | } 377 | 378 | void Activity::SetInstance(bool instance) 379 | { 380 | internal_.instance = instance; 381 | } 382 | 383 | bool Activity::GetInstance() const 384 | { 385 | return internal_.instance != 0; 386 | } 387 | 388 | void Presence::SetStatus(Status status) 389 | { 390 | internal_.status = static_cast(status); 391 | } 392 | 393 | Status Presence::GetStatus() const 394 | { 395 | return static_cast(internal_.status); 396 | } 397 | 398 | Activity& Presence::GetActivity() 399 | { 400 | return reinterpret_cast(internal_.activity); 401 | } 402 | 403 | Activity const& Presence::GetActivity() const 404 | { 405 | return reinterpret_cast(internal_.activity); 406 | } 407 | 408 | void Relationship::SetType(RelationshipType type) 409 | { 410 | internal_.type = static_cast(type); 411 | } 412 | 413 | RelationshipType Relationship::GetType() const 414 | { 415 | return static_cast(internal_.type); 416 | } 417 | 418 | User& Relationship::GetUser() 419 | { 420 | return reinterpret_cast(internal_.user); 421 | } 422 | 423 | User const& Relationship::GetUser() const 424 | { 425 | return reinterpret_cast(internal_.user); 426 | } 427 | 428 | Presence& Relationship::GetPresence() 429 | { 430 | return reinterpret_cast(internal_.presence); 431 | } 432 | 433 | Presence const& Relationship::GetPresence() const 434 | { 435 | return reinterpret_cast(internal_.presence); 436 | } 437 | 438 | void Lobby::SetId(LobbyId id) 439 | { 440 | internal_.id = id; 441 | } 442 | 443 | LobbyId Lobby::GetId() const 444 | { 445 | return internal_.id; 446 | } 447 | 448 | void Lobby::SetType(LobbyType type) 449 | { 450 | internal_.type = static_cast(type); 451 | } 452 | 453 | LobbyType Lobby::GetType() const 454 | { 455 | return static_cast(internal_.type); 456 | } 457 | 458 | void Lobby::SetOwnerId(UserId ownerId) 459 | { 460 | internal_.owner_id = ownerId; 461 | } 462 | 463 | UserId Lobby::GetOwnerId() const 464 | { 465 | return internal_.owner_id; 466 | } 467 | 468 | void Lobby::SetSecret(LobbySecret secret) 469 | { 470 | strncpy(internal_.secret, secret, 128); 471 | internal_.secret[128 - 1] = '\0'; 472 | } 473 | 474 | LobbySecret Lobby::GetSecret() const 475 | { 476 | return internal_.secret; 477 | } 478 | 479 | void Lobby::SetCapacity(std::uint32_t capacity) 480 | { 481 | internal_.capacity = capacity; 482 | } 483 | 484 | std::uint32_t Lobby::GetCapacity() const 485 | { 486 | return internal_.capacity; 487 | } 488 | 489 | void Lobby::SetLocked(bool locked) 490 | { 491 | internal_.locked = locked; 492 | } 493 | 494 | bool Lobby::GetLocked() const 495 | { 496 | return internal_.locked != 0; 497 | } 498 | 499 | void FileStat::SetFilename(char const* filename) 500 | { 501 | strncpy(internal_.filename, filename, 260); 502 | internal_.filename[260 - 1] = '\0'; 503 | } 504 | 505 | char const* FileStat::GetFilename() const 506 | { 507 | return internal_.filename; 508 | } 509 | 510 | void FileStat::SetSize(std::uint64_t size) 511 | { 512 | internal_.size = size; 513 | } 514 | 515 | std::uint64_t FileStat::GetSize() const 516 | { 517 | return internal_.size; 518 | } 519 | 520 | void FileStat::SetLastModified(std::uint64_t lastModified) 521 | { 522 | internal_.last_modified = lastModified; 523 | } 524 | 525 | std::uint64_t FileStat::GetLastModified() const 526 | { 527 | return internal_.last_modified; 528 | } 529 | 530 | void Entitlement::SetId(Snowflake id) 531 | { 532 | internal_.id = id; 533 | } 534 | 535 | Snowflake Entitlement::GetId() const 536 | { 537 | return internal_.id; 538 | } 539 | 540 | void Entitlement::SetType(EntitlementType type) 541 | { 542 | internal_.type = static_cast(type); 543 | } 544 | 545 | EntitlementType Entitlement::GetType() const 546 | { 547 | return static_cast(internal_.type); 548 | } 549 | 550 | void Entitlement::SetSkuId(Snowflake skuId) 551 | { 552 | internal_.sku_id = skuId; 553 | } 554 | 555 | Snowflake Entitlement::GetSkuId() const 556 | { 557 | return internal_.sku_id; 558 | } 559 | 560 | void SkuPrice::SetAmount(std::uint32_t amount) 561 | { 562 | internal_.amount = amount; 563 | } 564 | 565 | std::uint32_t SkuPrice::GetAmount() const 566 | { 567 | return internal_.amount; 568 | } 569 | 570 | void SkuPrice::SetCurrency(char const* currency) 571 | { 572 | strncpy(internal_.currency, currency, 16); 573 | internal_.currency[16 - 1] = '\0'; 574 | } 575 | 576 | char const* SkuPrice::GetCurrency() const 577 | { 578 | return internal_.currency; 579 | } 580 | 581 | void Sku::SetId(Snowflake id) 582 | { 583 | internal_.id = id; 584 | } 585 | 586 | Snowflake Sku::GetId() const 587 | { 588 | return internal_.id; 589 | } 590 | 591 | void Sku::SetType(SkuType type) 592 | { 593 | internal_.type = static_cast(type); 594 | } 595 | 596 | SkuType Sku::GetType() const 597 | { 598 | return static_cast(internal_.type); 599 | } 600 | 601 | void Sku::SetName(char const* name) 602 | { 603 | strncpy(internal_.name, name, 256); 604 | internal_.name[256 - 1] = '\0'; 605 | } 606 | 607 | char const* Sku::GetName() const 608 | { 609 | return internal_.name; 610 | } 611 | 612 | SkuPrice& Sku::GetPrice() 613 | { 614 | return reinterpret_cast(internal_.price); 615 | } 616 | 617 | SkuPrice const& Sku::GetPrice() const 618 | { 619 | return reinterpret_cast(internal_.price); 620 | } 621 | 622 | void InputMode::SetType(InputModeType type) 623 | { 624 | internal_.type = static_cast(type); 625 | } 626 | 627 | InputModeType InputMode::GetType() const 628 | { 629 | return static_cast(internal_.type); 630 | } 631 | 632 | void InputMode::SetShortcut(char const* shortcut) 633 | { 634 | strncpy(internal_.shortcut, shortcut, 256); 635 | internal_.shortcut[256 - 1] = '\0'; 636 | } 637 | 638 | char const* InputMode::GetShortcut() const 639 | { 640 | return internal_.shortcut; 641 | } 642 | 643 | void UserAchievement::SetUserId(Snowflake userId) 644 | { 645 | internal_.user_id = userId; 646 | } 647 | 648 | Snowflake UserAchievement::GetUserId() const 649 | { 650 | return internal_.user_id; 651 | } 652 | 653 | void UserAchievement::SetAchievementId(Snowflake achievementId) 654 | { 655 | internal_.achievement_id = achievementId; 656 | } 657 | 658 | Snowflake UserAchievement::GetAchievementId() const 659 | { 660 | return internal_.achievement_id; 661 | } 662 | 663 | void UserAchievement::SetPercentComplete(std::uint8_t percentComplete) 664 | { 665 | internal_.percent_complete = percentComplete; 666 | } 667 | 668 | std::uint8_t UserAchievement::GetPercentComplete() const 669 | { 670 | return internal_.percent_complete; 671 | } 672 | 673 | void UserAchievement::SetUnlockedAt(DateTime unlockedAt) 674 | { 675 | strncpy(internal_.unlocked_at, unlockedAt, 64); 676 | internal_.unlocked_at[64 - 1] = '\0'; 677 | } 678 | 679 | DateTime UserAchievement::GetUnlockedAt() const 680 | { 681 | return internal_.unlocked_at; 682 | } 683 | 684 | Result LobbyTransaction::SetType(LobbyType type) 685 | { 686 | auto result = internal_->set_type(internal_, static_cast(type)); 687 | return static_cast(result); 688 | } 689 | 690 | Result LobbyTransaction::SetOwner(UserId ownerId) 691 | { 692 | auto result = internal_->set_owner(internal_, ownerId); 693 | return static_cast(result); 694 | } 695 | 696 | Result LobbyTransaction::SetCapacity(std::uint32_t capacity) 697 | { 698 | auto result = internal_->set_capacity(internal_, capacity); 699 | return static_cast(result); 700 | } 701 | 702 | Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value) 703 | { 704 | auto result = 705 | internal_->set_metadata(internal_, const_cast(key), const_cast(value)); 706 | return static_cast(result); 707 | } 708 | 709 | Result LobbyTransaction::DeleteMetadata(MetadataKey key) 710 | { 711 | auto result = internal_->delete_metadata(internal_, const_cast(key)); 712 | return static_cast(result); 713 | } 714 | 715 | Result LobbyTransaction::SetLocked(bool locked) 716 | { 717 | auto result = internal_->set_locked(internal_, (locked ? 1 : 0)); 718 | return static_cast(result); 719 | } 720 | 721 | Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value) 722 | { 723 | auto result = 724 | internal_->set_metadata(internal_, const_cast(key), const_cast(value)); 725 | return static_cast(result); 726 | } 727 | 728 | Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key) 729 | { 730 | auto result = internal_->delete_metadata(internal_, const_cast(key)); 731 | return static_cast(result); 732 | } 733 | 734 | Result LobbySearchQuery::Filter(MetadataKey key, 735 | LobbySearchComparison comparison, 736 | LobbySearchCast cast, 737 | MetadataValue value) 738 | { 739 | auto result = internal_->filter(internal_, 740 | const_cast(key), 741 | static_cast(comparison), 742 | static_cast(cast), 743 | const_cast(value)); 744 | return static_cast(result); 745 | } 746 | 747 | Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value) 748 | { 749 | auto result = internal_->sort(internal_, 750 | const_cast(key), 751 | static_cast(cast), 752 | const_cast(value)); 753 | return static_cast(result); 754 | } 755 | 756 | Result LobbySearchQuery::Limit(std::uint32_t limit) 757 | { 758 | auto result = internal_->limit(internal_, limit); 759 | return static_cast(result); 760 | } 761 | 762 | Result LobbySearchQuery::Distance(LobbySearchDistance distance) 763 | { 764 | auto result = 765 | internal_->distance(internal_, static_cast(distance)); 766 | return static_cast(result); 767 | } 768 | 769 | } // namespace discord 770 | --------------------------------------------------------------------------------