Skip to content

add: show YTM user pfp / name in Discord RPC#4103

Open
noxygalaxy wants to merge 8 commits intopear-devs:masterfrom
noxygalaxy:master
Open

add: show YTM user pfp / name in Discord RPC#4103
noxygalaxy wants to merge 8 commits intopear-devs:masterfrom
noxygalaxy:master

Conversation

@noxygalaxy
Copy link
Copy Markdown

@noxygalaxy noxygalaxy commented Nov 22, 2025

<3

Summary by CodeRabbit

  • New Features
    • Discord Rich Presence can include the application user’s avatar and name.
    • New toggle in the Discord menu to enable/disable showing YouTube/application user info.
    • Automatic background extraction of YouTube account name and avatar to populate Rich Presence.
    • Localized strings added for English, Russian, and Ukrainian for the new UI text.

@JellyBrick JellyBrick added enhancement New feature or request need rebase This plugin need rebase labels Jan 29, 2026
@JellyBrick JellyBrick requested a review from Copilot January 29, 2026 08:54
@JellyBrick
Copy link
Copy Markdown
Member

Please fix these issues.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an option to display the current YouTube Music user’s avatar and name in the Discord Rich Presence, with a corresponding config flag and menu toggle.

Changes:

  • Extends the Discord plugin configuration with a showYouTubeUser flag and exposes it via the plugin’s menu.
  • Updates DiscordService to fetch the logged-in YT Music user (avatar + name) from the webview and surface it as the small image/text in Discord RPC.
  • Adds i18n strings for the new menu item in English, Russian, and Ukrainian locales.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/plugins/discord/menu.ts Adds a “Show YouTube Music user info” checkbox bound to the new showYouTubeUser config flag in the Discord plugin menu.
src/plugins/discord/index.ts Extends DiscordPluginConfig with showYouTubeUser: boolean and sets its default to true in the plugin’s config.
src/plugins/discord/discord-service.ts Stores fetched YT Music user info, fetches it from the renderer via executeJavaScript, and uses it for smallImageKey/smallImageText in Discord activity, with retry logic on failure.
src/i18n/resources/en.json Adds the plugins.discord.menu.show-youtube-user label in English.
src/i18n/resources/ru.json Adds the plugins.discord.menu.show-youtube-user label in Russian.
src/i18n/resources/uk.json Adds the plugins.discord.menu.show-youtube-user label in Ukrainian.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@noxygalaxy
Copy link
Copy Markdown
Author

Please fix these issues.

done :)

@noxygalaxy
Copy link
Copy Markdown
Author

so?

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d71a5b32-4793-466c-bba3-9acf53f56401

📥 Commits

Reviewing files that changed from the base of the PR and between e2489a0 and a653a99.

📒 Files selected for processing (1)
  • src/plugins/discord/menu.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/plugins/discord/menu.ts

📝 Walkthrough

Walkthrough

Adds extraction and display of YouTube Music account info in Discord Rich Presence: new i18n strings, a plugin config option and timer key, DiscordService caching and payload changes (new public setApplicationUser), an IPC listener for discord:youtube-info, a menu checkbox to toggle the feature, and an Electron preload script that scrapes YouTube Music for user avatar/name and sends it to the main process.

Changes

Cohort / File(s) Summary
Internationalization
src/i18n/resources/en.json, src/i18n/resources/ru.json, src/i18n/resources/uk.json
Added plugins.discord.backend.show-application-user translations (EN, RU, UK) with {{applicationName}}.
Constants & Config
src/plugins/discord/constants.ts, src/plugins/discord/index.ts
Added TimerKey.YouTubeFetchRetry enum value and new plugin config property showApplicationUser with default true; plugin now includes a preload entry.
Discord Service
src/plugins/discord/discord-service.ts
Added cached applicationUser, public setApplicationUser(user: {name, avatar}), reset logic in resetInfo(), helpers for avatar/name, and updated Rich Presence payload to include small image key/text when showApplicationUser is enabled.
Main & Menu
src/plugins/discord/main.ts, src/plugins/discord/menu.ts
Registered IPC listener for discord:youtube-info (forwards info to Discord service) and added a menu checkbox item plugins.discord.menu.show-application-user bound to config.showApplicationUser to toggle the feature.
Preload / YouTube scraping
src/plugins/discord/preload.ts
New Electron preload preload with start() using MutationObserver and polling (up to 20 attempts) to extract avatar src and account name from YouTube Music UI; sends { name, avatar } via ipcRenderer.send('discord:youtube-info', ...). Errors are logged and aborted without sending.

Sequence Diagram

sequenceDiagram
    participant YTMusic as "YouTube Music UI"
    participant Preload as "Preload Script"
    participant IPCRenderer as "ipcRenderer"
    participant Main as "Main Process"
    participant DiscordSvc as "DiscordService"
    participant RPC as "Discord RPC"

    Preload->>YTMusic: query avatar & settings button
    YTMusic-->>Preload: avatar src / account name (polled)
    Preload->>Preload: assemble {name, avatar}
    Preload->>IPCRenderer: send('discord:youtube-info', {name, avatar})
    IPCRenderer->>Main: deliver IPC event
    Main->>DiscordSvc: setApplicationUser({name, avatar})
    DiscordSvc->>DiscordSvc: cache user & updateActivity()
    DiscordSvc->>RPC: update Rich Presence (smallImageKey/text)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through DOM and midnight code,
Found an avatar where YT shadows flowed,
I sent a ping down the tiny wire,
Now Discord shows a friend entire —
Rabbit cheers, carrot-powered mode! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Discord Rich Presence support to display the YouTube Music user's profile picture and name.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@JellyBrick JellyBrick removed the need rebase This plugin need rebase label Apr 12, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/discord/discord-service.ts (1)

185-193: ⚠️ Potential issue | 🔴 Critical

Undefined identifier in dev check at Line 191.

The module imports is from electron-is, but this block calls electronIs.dev().

Suggested fix
-    if (electronIs.dev()) {
+    if (is.dev()) {
       console.log(LoggerPrefix, t('plugins.discord.backend.disconnected'));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/discord/discord-service.ts` around lines 185 - 193, The dev-mode
check inside resetInfo uses an undefined identifier electronIs.dev(); update it
to use the actual imported symbol (is.dev()) or adjust the import to match the
usage; specifically modify the dev check in the resetInfo method so it calls
is.dev() (or rename the import to electronIs) and keep the rest of the block
(console.log(LoggerPrefix, t(...))) unchanged so the timerManager.clearAll(),
ready/youtubeUser/lastSongInfo resets remain intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/discord/discord-service.ts`:
- Around line 439-459: The setYouTubeUser method references an undefined dev()
function; replace those calls with the imported helper is.dev() so logging uses
the correct helper. Update occurrences inside setYouTubeUser (the dev() checks
at the early-return warning and the verbose console logs) to use is.dev(),
ensuring the method uses the module's is helper consistently when gating
console.warn/console.log.
- Around line 132-137: The conditional is using the wrong config flag: replace
references to config.showYouTubeUser with the correct config.showApplicationUser
so the smallImageKey and smallImageText fields honor the plugin schema; update
the two occurrences around smallImageKey and smallImageText (which call
getYouTubeUserAvatar() and getYouTubeUserName()) to check
config.showApplicationUser instead.

In `@src/plugins/discord/menu.ts`:
- Around line 83-90: The menu item is using the wrong i18n and config keys;
update the MenuItem's label, checked property, and setConfig call to use the
defined keys for the application user instead of the YouTube user—replace
t('plugins.discord.menu.show-youtube-user') with
t('plugins.discord.menu.show-application-user'), use config.showApplicationUser
(not config.showYouTubeUser) for the checked value, and call setConfig({
showApplicationUser: item.checked }) inside the click handler so the i18n and
config keys (used where t, config, and setConfig are referenced) are consistent.

In `@src/plugins/discord/preload.ts`:
- Around line 5-108: Gate the scraping by checking the Discord integration
toggle before doing any DOM queries or sending data: in preload.start() (and
before findUserInfo() is ever called), query the main process or a safe
preload-exposed API (e.g., via ipcRenderer.invoke('discord:is-enabled') or a
preload-exposed window API) to see if the Discord option is enabled; if it is
false, do not create the MutationObserver, do not call findUserInfo(), and do
not register the DOMContentLoaded handler. Also update findUserInfo() to avoid
calling ipcRenderer.send('discord:youtube-info', ...) unless the enabled check
passed so no account data is collected or transmitted when the toggle is off.
- Around line 41-72: The code currently treats the placeholder 'Pear Desktop
User' as a successful lookup and always sends
ipcRenderer.send('discord:youtube-info', { name, avatar }) and returns true if
an avatar exists; change this so you only send the info and return true when you
actually located a real account name. In the block that queries
accountNameElement (look for usages of settingsButton and accountNameElement),
set a flag (e.g., foundName) when you successfully extract textContent or title,
and after the wait-loop check that flag (or that name !== 'Pear Desktop User')
before calling ipcRenderer.send('discord:youtube-info', ...); if not found,
avoid sending the placeholder, return false (or send a different payload
indicating no name) so RPC doesn't report a fake name.
- Around line 79-100: findUserInfo is racy: multiple observer callbacks and the
immediate call can re-enter it and emit duplicate discord:youtube-info events.
Add a re-entry guard (e.g., isFinding boolean or inFlight Promise) inside
findUserInfo to return immediately if a lookup is already running, set it at
start and clear it in finally; additionally track whether the YouTube info has
already been emitted (e.g., foundSent or lastEmittedId) so repeated successful
lookups don’t re-emit; ensure observer.disconnect() is still called on success
and that startObserver uses the guarded findUserInfo to avoid overlapping calls.

---

Outside diff comments:
In `@src/plugins/discord/discord-service.ts`:
- Around line 185-193: The dev-mode check inside resetInfo uses an undefined
identifier electronIs.dev(); update it to use the actual imported symbol
(is.dev()) or adjust the import to match the usage; specifically modify the dev
check in the resetInfo method so it calls is.dev() (or rename the import to
electronIs) and keep the rest of the block (console.log(LoggerPrefix, t(...)))
unchanged so the timerManager.clearAll(), ready/youtubeUser/lastSongInfo resets
remain intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6cdac46-bbaf-442a-b107-f850a06692c3

📥 Commits

Reviewing files that changed from the base of the PR and between f2e6126 and 2e7fd48.

📒 Files selected for processing (9)
  • src/i18n/resources/en.json
  • src/i18n/resources/ru.json
  • src/i18n/resources/uk.json
  • src/plugins/discord/constants.ts
  • src/plugins/discord/discord-service.ts
  • src/plugins/discord/index.ts
  • src/plugins/discord/main.ts
  • src/plugins/discord/menu.ts
  • src/plugins/discord/preload.ts

Comment on lines +5 to +108
export const preload = createPreload({
start() {
let checkCount = 0;
const maxChecks = 20;

const findUserInfo = async () => {
try {
let avatar: string | null = null;

// Find avatar first - this is always visible
const accountButton =
document.querySelector<HTMLImageElement>(
'ytmusic-settings-button img#img',
) ||
document.querySelector<HTMLImageElement>(
'ytmusic-settings-button yt-img-shadow img',
) ||
document.querySelector<HTMLImageElement>(
'ytmusic-settings-button img',
);

if (accountButton) {
avatar = accountButton.src || accountButton.getAttribute('src');
}

if (!avatar || avatar.startsWith('data:')) {
return false;
}

// Now get the username by clicking the settings button if we don't have it
const settingsButton =
document.querySelector('ytmusic-settings-button button') ||
document.querySelector(
'ytmusic-settings-button tp-yt-paper-icon-button',
);

let name = 'Pear Desktop User';

if (settingsButton) {
// Click to open the menu
(settingsButton as HTMLElement).click();

// Wait for the menu to appear
for (let i = 0; i < 20; i++) {
await new Promise((resolve) => setTimeout(resolve, 50));

const accountNameElement =
document.querySelector(
'ytd-active-account-header-renderer #account-name',
) || document.querySelector('yt-formatted-string#account-name');

if (accountNameElement) {
name =
accountNameElement.textContent?.trim() ||
accountNameElement.getAttribute('title') ||
name;
break;
}
}

// Close the menu by pressing Escape
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }),
);
}

ipcRenderer.send('discord:youtube-info', { name, avatar });
return true;
} catch (e) {
console.error('Failed to fetch YouTube user info:', e);
return false;
}
};

const observer = new MutationObserver(() => {
if (checkCount >= maxChecks) {
observer.disconnect();
return;
}

findUserInfo().then((found) => {
if (found) {
observer.disconnect();
}
});
checkCount++;
});

const startObserver = () => {
observer.observe(document.body, {
childList: true,
subtree: true,
});

// Also try immediately
findUserInfo();
};

if (document.body) {
startObserver();
} else {
document.addEventListener('DOMContentLoaded', startObserver);
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Gate the scraping behind the user-facing toggle.

This preload harvests the YTM account name/avatar on every startup. If the new Discord option is off, the data is still collected and sent to the main process anyway, which weakens the privacy boundary for an opt-in feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/discord/preload.ts` around lines 5 - 108, Gate the scraping by
checking the Discord integration toggle before doing any DOM queries or sending
data: in preload.start() (and before findUserInfo() is ever called), query the
main process or a safe preload-exposed API (e.g., via
ipcRenderer.invoke('discord:is-enabled') or a preload-exposed window API) to see
if the Discord option is enabled; if it is false, do not create the
MutationObserver, do not call findUserInfo(), and do not register the
DOMContentLoaded handler. Also update findUserInfo() to avoid calling
ipcRenderer.send('discord:youtube-info', ...) unless the enabled check passed so
no account data is collected or transmitted when the toggle is off.

Comment on lines +41 to +72
let name = 'Pear Desktop User';

if (settingsButton) {
// Click to open the menu
(settingsButton as HTMLElement).click();

// Wait for the menu to appear
for (let i = 0; i < 20; i++) {
await new Promise((resolve) => setTimeout(resolve, 50));

const accountNameElement =
document.querySelector(
'ytd-active-account-header-renderer #account-name',
) || document.querySelector('yt-formatted-string#account-name');

if (accountNameElement) {
name =
accountNameElement.textContent?.trim() ||
accountNameElement.getAttribute('title') ||
name;
break;
}
}

// Close the menu by pressing Escape
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }),
);
}

ipcRenderer.send('discord:youtube-info', { name, avatar });
return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't treat the placeholder as a successful account-name lookup.

At Line 71 this sends { name: 'Pear Desktop User', avatar } and returns success whenever an avatar exists. If the menu selector misses or loads too slowly, the observer stops and the RPC can get stuck with a fake name instead of the actual YTM account name.

Suggested fix
-        let name = 'Pear Desktop User';
+        let name: string | null = null;
...
             if (accountNameElement) {
               name =
                 accountNameElement.textContent?.trim() ||
                 accountNameElement.getAttribute('title') ||
                 name;
               break;
             }
           }
...
-        ipcRenderer.send('discord:youtube-info', { name, avatar });
+        if (!name) {
+          return false;
+        }
+
+        ipcRenderer.send('discord:youtube-info', { name, avatar });
         return true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let name = 'Pear Desktop User';
if (settingsButton) {
// Click to open the menu
(settingsButton as HTMLElement).click();
// Wait for the menu to appear
for (let i = 0; i < 20; i++) {
await new Promise((resolve) => setTimeout(resolve, 50));
const accountNameElement =
document.querySelector(
'ytd-active-account-header-renderer #account-name',
) || document.querySelector('yt-formatted-string#account-name');
if (accountNameElement) {
name =
accountNameElement.textContent?.trim() ||
accountNameElement.getAttribute('title') ||
name;
break;
}
}
// Close the menu by pressing Escape
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }),
);
}
ipcRenderer.send('discord:youtube-info', { name, avatar });
return true;
let name: string | null = null;
if (settingsButton) {
// Click to open the menu
(settingsButton as HTMLElement).click();
// Wait for the menu to appear
for (let i = 0; i < 20; i++) {
await new Promise((resolve) => setTimeout(resolve, 50));
const accountNameElement =
document.querySelector(
'ytd-active-account-header-renderer `#account-name`',
) || document.querySelector('yt-formatted-string#account-name');
if (accountNameElement) {
name =
accountNameElement.textContent?.trim() ||
accountNameElement.getAttribute('title') ||
name;
break;
}
}
// Close the menu by pressing Escape
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }),
);
}
if (!name) {
return false;
}
ipcRenderer.send('discord:youtube-info', { name, avatar });
return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/discord/preload.ts` around lines 41 - 72, The code currently
treats the placeholder 'Pear Desktop User' as a successful lookup and always
sends ipcRenderer.send('discord:youtube-info', { name, avatar }) and returns
true if an avatar exists; change this so you only send the info and return true
when you actually located a real account name. In the block that queries
accountNameElement (look for usages of settingsButton and accountNameElement),
set a flag (e.g., foundName) when you successfully extract textContent or title,
and after the wait-loop check that flag (or that name !== 'Pear Desktop User')
before calling ipcRenderer.send('discord:youtube-info', ...); if not found,
avoid sending the placeholder, return false (or send a different payload
indicating no name) so RPC doesn't report a fake name.

Comment on lines +79 to +100
const observer = new MutationObserver(() => {
if (checkCount >= maxChecks) {
observer.disconnect();
return;
}

findUserInfo().then((found) => {
if (found) {
observer.disconnect();
}
});
checkCount++;
});

const startObserver = () => {
observer.observe(document.body, {
childList: true,
subtree: true,
});

// Also try immediately
findUserInfo();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard findUserInfo() against re-entry and duplicate sends.

The observer watches subtree mutations, and findUserInfo() itself opens/closes the menu, so it can trigger more observer callbacks while a previous lookup is still pending. Combined with the immediate call at Line 100, this can overlap lookups and emit discord:youtube-info multiple times.

Suggested fix
     let checkCount = 0;
     const maxChecks = 20;
+    let lookupInFlight = false;
+    let sent = false;

     const findUserInfo = async () => {
+      if (lookupInFlight || sent) {
+        return false;
+      }
+
+      lookupInFlight = true;
       try {
         let avatar: string | null = null;
...
-        ipcRenderer.send('discord:youtube-info', { name, avatar });
+        ipcRenderer.send('discord:youtube-info', { name, avatar });
+        sent = true;
         return true;
       } catch (e) {
         console.error('Failed to fetch YouTube user info:', e);
         return false;
+      } finally {
+        lookupInFlight = false;
       }
     };
...
     const startObserver = () => {
-      observer.observe(document.body, {
-        childList: true,
-        subtree: true,
-      });
-
-      // Also try immediately
-      findUserInfo();
+      findUserInfo().then((found) => {
+        if (found) {
+          observer.disconnect();
+          return;
+        }
+
+        observer.observe(document.body, {
+          childList: true,
+          subtree: true,
+        });
+      });
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/discord/preload.ts` around lines 79 - 100, findUserInfo is racy:
multiple observer callbacks and the immediate call can re-enter it and emit
duplicate discord:youtube-info events. Add a re-entry guard (e.g., isFinding
boolean or inFlight Promise) inside findUserInfo to return immediately if a
lookup is already running, set it at start and clear it in finally; additionally
track whether the YouTube info has already been emitted (e.g., foundSent or
lastEmittedId) so repeated successful lookups don’t re-emit; ensure
observer.disconnect() is still called on success and that startObserver uses the
guarded findUserInfo to avoid overlapping calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants