🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 7 Min Lesezeit
0

Why "must be subscribed to enter" is a checkbox I had to delete from a YouTube giveaway picker

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht
📺
dev.to

A YouTuber I know asked me to help pick a winner from a giveaway video fairly, because doing it by scrolling through comments and eyeballing one invites exactly the kind of "the mod's friend won" accusations you'd expect. I figured: grab the comments, roll a die, done — how hard can that be? Turns out, once you actually build it against the real YouTube Data API v3 instead of imagining how it probably works, there's pagination to chase, replies to exclude, duplicate commenters to catch, and one feature I had to rip out because it's technically impossible to build honestly.






Turning whatever URL someone pastes into a video ID



People paste video links in three different shapes — the desktop watch?v= link, the shortened youtu.be/ link, and the shorts/ link — and none of them go through a real URL parser, just string splitting on whichever marker is present:




CODE
let searchID = "";
if (searchKey.value.indexOf("v=") > -1) {
searchID = searchKey.value.split("v=")[1];
} else if (searchKey.value.indexOf("youtu.be") > -1) {
// https://youtu.be/KtkW3Hx0MvA
searchID = searchKey.value.split("youtu.be/")[1];
} else if (searchKey.value.indexOf("shorts") > -1) {
// https://www.youtube.com/shorts/QWUTKpZmSX0
searchID = searchKey.value.split("shorts/")[1];
}






This works for a clean link, but it doesn't strip anything that comes after the ID. Paste a link with a timestamp (&t=45s) or a share-tracking suffix (?si=xyz) and that junk rides along as part of searchID, gets sent straight to the video-lookup call, and comes back as a generic "couldn't find that video" error — which is technically true but tells the organizer nothing about why. I didn't catch this until someone pasted a link straight from the YouTube mobile app's share sheet, which appends a tracking param by default.






Paginating through the comments API without losing your place



YouTube's commentThreads endpoint hands back comments in pages of up to 100, with a nextPageToken when there's more. The fetch function just calls itself with that token until there isn't one:




CODE
const getComment = (searchID, pageToken = null) => {
isLoading.value = true;
$API.youtubeData.youtube_getComment(searchID, pageToken).then((res) => {
let { data } = res;
if (data && data.items) {
data.items.forEach((item) => {
comentData.push({
authorChannelId: item.snippet.topLevelComment.snippet.authorChannelId,
authorDisplayName: item.snippet.topLevelComment.snippet.authorDisplayName,
likeCount: item.snippet.topLevelComment.snippet.likeCount,
textOriginal: item.snippet.topLevelComment.snippet.textOriginal,
totalReplyCount: item.snippet.totalReplyCount,
// ...a few more fields for the table
});
});
if (data.nextPageToken) {
getComment(searchID, data.nextPageToken);
} else {
isLoading.value = false;
}
}
});
};






Notice everything is pulled from topLevelComment.snippet — never the replies themselves. totalReplyCount is captured just so it can be displayed in the table, but the actual reply text is never fetched. So if a giveaway's rule is "reply to the pinned comment to enter" rather than "leave a top-level comment," this tool structurally can't see those entries — it only ever eats top-level comments, which is also why the UI explicitly labels its count as "comments (excluding replies)."






One entry per commenter, not per comment



Before picking a winner, every comment runs through a filter chain, and the interesting one is duplicate removal:




CODE
const MessageFrom = new Set();
let filterList = comentData.filter(({ textDisplay, likeCount, authorChannelId, publishedAt }) => {
if (mustContain.value && textDisplay.indexOf(mustContainValue.value) == -1) return false;
if (mustLength.value && textDisplay.length < mustLengthValue.value) return false;
if (mustLike.value && likeCount * 1 < mustLikeValue.value) return false;

if (mustNoRepeat.value) {
if (MessageFrom.has(authorChannelId)) {
return false;
} else {
MessageFrom.add(authorChannelId);
}
}
// ...date-range check
return true;
});






Dedup is keyed on authorChannelId, not the display name — a name is just cosmetic text a user can change at any moment, so two comments from the same account could show two different names. The channel ID is the one thing that's actually stable. Since Array.filter walks the list in order, whichever comment from a given channel appears first is the one that survives; any later comment from that same account is silently dropped, keyword-spam or not.



The date-range check on the next line is the one piece of this function I'd flag if I were reviewing someone else's PR:




CODE
if (
(mustDate.value &&
new Date(new Date(mustDateStart.value).toDateString()).getTime() >
new Date(publishedAt).getTime()) ||
new Date(new Date(mustDateEnd.value).toDateString()).getTime() + 86400000 <
new Date(publishedAt).getTime()
)
return false;






The parentheses only wrap mustDate.value && startCheck — the end-date half of the || has no such guard, so it's evaluated regardless of whether the "date range" checkbox is even ticked. In practice it's harmless today only because an empty mustDateEnd produces Invalid Date, and NaN < anything is always false. But it's one stray value away from silently filtering out legitimate entries the moment someone fills in an end date without checking the box first.






Picking winners without replacement, the brute-force way



Once the pool is filtered, picking N winners isn't a shuffle — it's rejection sampling:




CODE
let real_index = [];
for (let i = 0; i < AwardNumber.value; i++) {
let now = random(filterList.length);
if (!real_index.includes(now)) {
real_index.push(now);
} else {
i--; // collision — roll again
}
}

function random(real_max) {
return Math.floor(Math.random() * (real_max - 0)) + 0;
}






Roll a random index; if it's already been picked, back the loop counter up and roll again. It's not a proper Fisher–Yates partial shuffle, and it doesn't need to be — the code already checked earlier that filterList.length >= AwardNumber.value, so the loop is guaranteed to eventually land on N distinct indexes, and for a typical giveaway (a handful of winners out of hundreds of eligible comments) collisions are rare enough that a plain retry loop is simpler to read than a real shuffle and costs nothing anyone would notice.






Where this quietly falls short



There's a checkbox still sitting in the template, commented out, for "must be subscribed to the channel" — with a note next to it admitting that anyone who hasn't made their subscriptions public gets treated as unsubscribed. I left it disabled on purpose: YouTube's API has no endpoint that tells you "does channel A have viewer B subscribed," because a user's subscription list is private by default and there's no way to check it from public comment data without that specific user handing over their own OAuth token. Any tool claiming to enforce a subscribe-to-enter rule automatically is either lying or quietly excluding most of your actual subscribers.



A couple other honest gotchas: the comments API has a daily quota, so a video with an unusually large comment count can exhaust it mid-fetch and just stop with a generic error. And dedup by channel ID stops someone from commenting five times on one account, but does nothing to stop someone running the exact same giveaway comment from two separate Google accounts — there's no way to detect that from a comment thread alone.



I cleaned this up into a small free tool if you run YouTube giveaways and want to skip building your own version: — 繁體中文


  • — English


  • — 한국어


  • — Русский


  • — Bahasa Indonesia


  • — Tiếng Việt


  • — Polski


  • — Italiano


  • — Nederlands


  • Інструмент для витягування коментарів на YouTube — Українська

  • Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
    Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 0%
    🟡 In Evaluierung 0%
    🟢 Keine Auswirkung 0%
    Spannende Innovation 0%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    3 Quellen
    GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
    1 Quelle
    Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
    1 Quelle
    Major AI platforms go down in unprecedented simultaneous outage