Skip to content

Commit bd337a9

Browse files
committed
Add opt-in YouTube like count updates
1 parent 9ec7292 commit bd337a9

9 files changed

Lines changed: 78 additions & 11 deletions

File tree

actions/EventFlowEditor.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3423,8 +3423,9 @@ class EventFlowEditor {
34233423
{ value: 'giftredemption', label: 'Gift Received (YouTube)' },
34243424
{ value: 'stream_online', label: 'Stream Online' },
34253425
{ value: 'stream_offline', label: 'Stream Offline' },
3426-
{ value: 'viewer_update', label: 'Viewer Count Update' },
3427-
{ value: 'follower_update', label: 'Follower Count Update' },
3426+
{ value: 'viewer_update', label: 'Viewer Count Update' },
3427+
{ value: 'likes_update', label: 'Video Like Count Update (YouTube)' },
3428+
{ value: 'follower_update', label: 'Follower Count Update' },
34283429
{ value: 'subscriber_update', label: 'Subscriber Count Update' },
34293430
{ value: 'ad_break', label: 'Ad Break (Twitch)' }
34303431
];

background.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5080,6 +5080,9 @@ chrome.runtime.onMessage.addListener(async function (request, sender, sendRespon
50805080
if (request.setting == "capturelikeevent") {
50815081
pushSettingChange();
50825082
}
5083+
if (request.setting == "captureyoutubelikes") {
5084+
pushSettingChange();
5085+
}
50835086
if (request.setting == "bttv") {
50845087
if (settings.bttv) {
50855088
clearAllWithPrefix("uid2bttv2.twitch:");

dock.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6507,6 +6507,24 @@ <h3>Status</h3>
65076507
if (data.type && data.event == "follower_update"){
65086508
followers[data.type] = parseInt(data.meta) || 0;
65096509
return true;
6510+
} else if (data.type && data.event == "likes_update"){
6511+
let likeCount = parseInt(data.meta, 10);
6512+
if (isNaN(likeCount) || likeCount < 0) return true;
6513+
let ele = topBarElement.querySelector("[data-youtube-like-count]");
6514+
if (!ele){
6515+
ele = document.createElement("div");
6516+
ele.dataset.youtubeLikeCount = "true";
6517+
ele.classList.add("viewerIcons");
6518+
ele.title = "YouTube video likes";
6519+
ele.style.backgroundImage = "url(./sources/images/youtube.png)";
6520+
topBarElement.prepend(ele);
6521+
}
6522+
ele.innerText = "♥ " + likeCount;
6523+
clearTimeout(ele.timeout);
6524+
ele.timeout = setTimeout(function(ele){
6525+
if (ele) ele.remove();
6526+
}, 130000, ele);
6527+
return true;
65106528
} else if (data.type && data.event == "viewer_update"){
65116529
viewers[data.type] = parseInt(data.meta) || 0;
65126530

docs/event-reference.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ <h2>Field Overview</h2>
316316
<h2>Meta Conventions</h2>
317317
<p>To keep dashboards and automations aligned, follow these conventions when extending <code>data.meta</code>:</p>
318318
<ul>
319-
<li><code>viewer_update</code>, <code>follower_update</code>, and <code>subscriber_update</code> use a plain integer <code>meta</code> value. The background script aggregates them into <code>viewer_updates</code> with an object keyed by <code>data.type</code>.</li>
319+
<li><code>viewer_update</code>, <code>follower_update</code>, <code>subscriber_update</code>, and <code>likes_update</code> use a plain integer <code>meta</code> value. The background script aggregates viewer counts into <code>viewer_updates</code> with an object keyed by <code>data.type</code>.</li>
320320
<li><code>video_stats</code> uses a structured <code>meta</code> object for external encoder/server health, including <code>provider</code>, <code>label</code>, <code>online</code>, <code>bitrateKbps</code>, <code>rttMs</code>, <code>bufferMs</code>, packet loss/drop counters, and optional codec details.</li>
321321
<li>Donation-style events include a descriptive object: for example <code>{ amount, currency, supporter }</code> for Kick, <code>{ bits }</code> for Twitch cheers, <code>{ supporter, eventType }</code> for YouTube memberships.</li>
322322
<li>Raids and hosts pass <code>{ fromId, fromLogin, viewers }</code> so overlays can display both streamer name and audience size.</li>
@@ -466,6 +466,11 @@ <h3>YouTube – Websocket/Data API Capture</h3>
466466
<td>Streaming stats (concurrent viewers) when viewer reporting is enabled.</td>
467467
<td><code>meta</code> is integer count; mirrors DOM scripting so downstream consumers can merge both flows.</td>
468468
</tr>
469+
<tr>
470+
<td class="status-event"><code>likes_update</code></td>
471+
<td>Official video statistics poll when <strong>Show YouTube video like count in dashboard</strong> is enabled.</td>
472+
<td><code>meta</code> is the current video like-count integer. It emits when the count changes and periodically while unchanged so dashboard state stays fresh.</td>
473+
</tr>
469474
<tr>
470475
<td class="status-event"><code>subscriber_update</code></td>
471476
<td>Channel stats poll (subscribers) when <code>showsubscount</code> not explicitly disabled.</td>
@@ -1892,7 +1897,7 @@ <h3>Tracked Mismatches and Voids</h3>
18921897
</tr>
18931898
<tr>
18941899
<td>YouTube: Standard vs Websocket</td>
1895-
<td>Shared: <code>superchat</code>, <code>supersticker</code>, <code>jeweldonation</code>, <code>sponsorship</code>, <code>resub</code>, <code>giftpurchase</code>, <code>giftredemption</code>, <code>viewer_update</code>. Standard-only: <code>thankyou</code>, <code>redirect</code>. Websocket-only: <code>membermilestone</code>, <code>new_follower</code>, <code>subscriber_update</code>, <code>view_update</code>.</td>
1900+
<td>Shared: <code>superchat</code>, <code>supersticker</code>, <code>jeweldonation</code>, <code>sponsorship</code>, <code>resub</code>, <code>giftpurchase</code>, <code>giftredemption</code>, <code>viewer_update</code>. Standard-only: <code>thankyou</code>, <code>redirect</code>. Websocket-only: <code>membermilestone</code>, <code>new_follower</code>, <code>subscriber_update</code>, <code>view_update</code>, <code>likes_update</code> (opt-in).</td>
18961901
<td>Core member/event names are aligned across both; Super Chat, Super Sticker, and Jewels use <code>hasDonation</code>, while membership gift purchases/redemptions do not.</td>
18971902
</tr>
18981903
<tr>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"test:rag:e2e": "node tests/rag-e2e.test.js",
77
"test:rag:scale": "node tests/rag-scale-benchmark.test.js",
88
"test:streamdeck-router": "node tests/streamdeck-remote-control-router.test.js",
9+
"test:youtube-likes": "node tests/youtube-like-count.test.js",
910
"test:aiprompt:smoke": "node scripts/playwright-aiprompt-smoke.cjs",
1011
"test:xss:sanitizer": "node tests/xss-sanitizer.test.js",
1112
"test:aiprompt:adversarial": "node scripts/aiprompt-ssnpatch-adversarial-e2e.cjs",

popup.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6933,6 +6933,14 @@ <h3>Custom Injection</h3>
69336933
</label>
69346934
<img class="icon" src="./sources/images/tiktok.png" style="display: inline-block;" />
69356935
<span> Show TikTok likes in main chat/events </span>
6936+
</div>
6937+
<div title="Poll the official YouTube API and show the current video like count in the dashboard.">
6938+
<label class="switch">
6939+
<input type="checkbox" data-setting="captureyoutubelikes" />
6940+
<span class="slider round"></span>
6941+
</label>
6942+
<img class="icon" src="./sources/images/youtube.png" style="display: inline-block;" />
6943+
<span> Show YouTube video like count in dashboard </span>
69366944
</div>
69376945
<div title="Do not treat coin-bought items as gifts on Tiktok, such as roses">
69386946
<label class="switch">

popup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4460,6 +4460,7 @@ var BEGINNER_ADVANCED_OPTION_SELECTORS = {
44604460
'[data-setting="notiktoklinks"]',
44614461
'[data-setting="capturejoinedevent"]',
44624462
'[data-setting="capturelikeevent"]',
4463+
'[data-setting="captureyoutubelikes"]',
44634464
'[data-setting="notiktokdonations"]',
44644465
'[data-setting="disabletiktokpoke"]',
44654466
'[data-setting="blockpremiumshorts"]',

shared/config/settingsDefinitions.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ const SETTINGS_DEFINITIONS = Object.freeze({
209209
category: "custom_injection",
210210
description: "Allow 'liked' stream events in TikTok (high volume)"
211211
},
212+
"captureyoutubelikes": {
213+
type: "boolean",
214+
category: "youtube_api",
215+
description: "Send the current YouTube video like count to the dashboard (polled every 30 seconds)"
216+
},
212217
"chatbotHistoryTotal": {
213218
type: "number",
214219
category: "chat_bot",

sources/websocket/youtube.html

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,9 @@ <h4>Advanced Controls</h4>
548548
let recentSubscriberPollingGeneration = 0;
549549
let fetchTimeout; // Track fetch timeout to clear on disconnect
550550
let lastKnownViewers = null;
551+
var lastKnownLikes = null;
552+
var lastLikesEmitTime = 0;
553+
const YT_LIKES_HEARTBEAT_MS = 90 * 1000;
551554
var viewerCountFallbackVideoId = '';
552555
var viewerCountMissingPolls = 0;
553556
var viewerCountFallbackLastAttempt = 0;
@@ -5409,7 +5412,7 @@ <h4>Advanced Controls</h4>
54095412
shouldEmitViewerUpdate = true;
54105413
}
54115414
}
5412-
if (stats.statistics && stats.statistics.likeCount) {
5415+
if (stats.statistics && stats.statistics.likeCount !== undefined) {
54135416
updateStats('likes_update', { meta: stats.statistics.likeCount });
54145417
}
54155418
}
@@ -5429,8 +5432,13 @@ <h4>Advanced Controls</h4>
54295432
);
54305433
}
54315434

5435+
function isYouTubeLikeCountEnabled() {
5436+
const currentSettings = settings && typeof settings === 'object' ? settings : {};
5437+
return Boolean(currentSettings.captureyoutubelikes);
5438+
}
5439+
54325440
function shouldPollStreamStats() {
5433-
return isViewerTrackingEnabled();
5441+
return isViewerTrackingEnabled() || isYouTubeLikeCountEnabled();
54345442
}
54355443

54365444
function stopStreamStatsPolling() {
@@ -5442,7 +5450,14 @@ <h4>Advanced Controls</h4>
54425450

54435451
function syncStreamStatsPolling() {
54445452
stopStreamStatsPolling();
5445-
if (!currentStream || !currentStream.id || !liveChatId || !shouldPollStreamStats()) {
5453+
if (!isYouTubeLikeCountEnabled()) {
5454+
lastKnownLikes = null;
5455+
lastLikesEmitTime = 0;
5456+
}
5457+
if (!currentStream || !currentStream.id || !shouldPollStreamStats()) {
5458+
return;
5459+
}
5460+
if (!liveChatId && !isYouTubeLikeCountEnabled()) {
54465461
return;
54475462
}
54485463
getStreamStats();
@@ -5495,6 +5510,8 @@ <h4>Advanced Controls</h4>
54955510
fetchTimeout = null;
54965511
liveChatPollingActive = false;
54975512
lastKnownViewers = null;
5513+
lastKnownLikes = null;
5514+
lastLikesEmitTime = 0;
54985515
resetViewerCountFallbackState(null);
54995516

55005517
// Track disconnection time and video ID
@@ -5737,7 +5754,7 @@ <h4>Advanced Controls</h4>
57375754
const viewerCount = document.getElementById('viewer-count');
57385755
if (viewerCount) viewerCount.textContent = '-';
57395756
}
5740-
if (streamInfoToUse.statistics && streamInfoToUse.statistics.likeCount) {
5757+
if (streamInfoToUse.statistics && streamInfoToUse.statistics.likeCount !== undefined) {
57415758
updateStats('likes_update', { meta: streamInfoToUse.statistics.likeCount });
57425759
} else {
57435760
document.getElementById('likes-count').textContent = 'N/A';
@@ -5788,7 +5805,7 @@ <h4>Advanced Controls</h4>
57885805
stopStreamStatsPolling();
57895806
if (channelStatsInterval) clearInterval(channelStatsInterval);
57905807

5791-
// Stream stats (viewers & likes) - poll only when viewer tracking is enabled
5808+
// Stream stats (viewers and opt-in likes)
57925809
syncStreamStatsPolling();
57935810

57945811
// Channel stats (subscribers) - every 30 minutes
@@ -6536,8 +6553,16 @@ <h4>Advanced Controls</h4>
65366553
document.getElementById('subscriber-count').textContent = data.meta;
65376554
break;
65386555
case 'likes_update':
6539-
document.getElementById('likes-count').textContent = data.meta;
6540-
//pushMessage({ type: youtubeShorts ? "youtubeshorts" : "youtube", event: 'likes_update', meta: data.meta });
6556+
var likeCount = normalizeViewerCount(data.meta);
6557+
document.getElementById('likes-count').textContent = likeCount !== null ? likeCount : '-';
6558+
if (isYouTubeLikeCountEnabled() && likeCount !== null) {
6559+
var now = Date.now();
6560+
if (likeCount !== lastKnownLikes || now - lastLikesEmitTime >= YT_LIKES_HEARTBEAT_MS) {
6561+
lastKnownLikes = likeCount;
6562+
lastLikesEmitTime = now;
6563+
pushMessage(applySourceIdentity({ type: youtubeShorts ? "youtubeshorts" : "youtube", event: 'likes_update', meta: likeCount }));
6564+
}
6565+
}
65416566
break;
65426567
}
65436568
}

0 commit comments

Comments
 (0)