import { Masterchat, stringify } from "masterchat";
const { title, channelId, channelName } = await Masterchat.init("<videoId>");
console.log(`info: ${title} @ ${channelName} (${channelId})`);
import { Masterchat, stringify } from "masterchat";
const mc = await Masterchat.init("<videoId>");
// Listen for live chat
mc.on("chat", (chat) => {
console.log(chat.authorName, stringify(chat.message));
});
// Listen for any events
// See below for a list of available action types
mc.on("actions", (actions) => {
const chats = actions.filter(
(action) => action.type === "addChatItemAction"
);
const superChats = actions.filter(
(action) => action.type === "addSuperChatItemAction"
);
const superStickers = actions.filter(
(action) => action.type === "addSuperStickerItemAction"
);
// ...
});
// Handle errors
mc.on("error", (err) => {
console.log(err.code);
// "disabled" => Live chat is disabled
// "membersOnly" => No permission (members-only)
// "private" => No permission (private video)
// "unavailable" => Deleted OR wrong video id
// "unarchived" => Live stream recording is not available
// "denied" => Access denied (429)
// "invalid" => Invalid request
});
// Handle end event
mc.on("end", () => {
console.log("Live stream has ended");
}
// Start polling live chat API
mc.listen();
import { Masterchat, MasterchatError, stringify } from "masterchat";
try {
const mc = await Masterchat.init("<videoId>");
const chats = mc
.iter()
.filter((action) => action.type === "addChatItemAction");
for await (const chat of chats) {
console.log(`${chat.authorName}: ${stringify(chat.message)}`);
}
} catch (err) {
// Handle errors
if (err instanceof MasterchatError) {
console.log(err.code);
// "disabled" => Live chat is disabled
// "membersOnly" => No permission (members-only)
// "private" => No permission (private video)
// "unavailable" => Deleted OR wrong video id
// "unarchived" => Live stream recording is not available
// "denied" => Access denied (429)
// "invalid" => Invalid request
return;
}
throw err;
}
console.log("Live stream has ended");
import { Masterchat } from "masterchat";
import { appendFile, writeFile, readFile } from "node:fs/promises";
const mc = await Masterchat.init("<videoId>");
await mc
.iter()
.filter((action) => action.type === "addChatItemAction") // only chat events
.map((chat) => JSON.stringify(chat) + "\n") // convert to JSONL
.forEach((jsonl) => appendFile("./chats.jsonl", jsonl)); // append to the file
import { Masterchat, stringify } from "masterchat";
import { isSpam } from "spamreaper";
// `credentials` is an object containing YouTube session cookie, or a base64-encoded JSON string of the object
const credentials = {
SAPISID: "<value>",
APISID: "<value>",
HSID: "<value>",
SID: "<value>",
SSID: "<value>",
};
const mc = await Masterchat.init("<videoId>", { credentials });
const chats = mc.iter().filter((action) => action.type === "addChatItemAction");
for await (const chat of chats) {
const message = stringify(chat.message, {
// omit emojis
emojiHandler: (emoji) => "",
});
// delete chat
// if flagged as spam by Spamreaper
// or contains "UGLY"
if (isSpam(message) || /UGLY/.test(message)) {
await mc.remove(chat.id);
}
}
import { Masterchat } from "masterchat";
const mc = await Masterchat.init("<videoId>");
// Iterate over all comments
let res = await mc.getComments({ top: true });
while (true) {
console.log(res.comments);
if (!res.next) break;
res = await res.next();
}
// Get comment by id
const comment = await mc.getComment("<commentId>");
console.log(comment);
import { Masterchat, stringify } from "masterchat";
const mc = await Masterchat.init("<videoId>");
const transcript = await mc.getTranscript();
for (const item of transcript) {
console.log(item.startMs, stringify(item.snippet));
}
For faster instantiation, you can totally skip fetching metadata (channelId, title, isLive) phase which happens behind Masterchat.init()
. To do this, use new Masterchat(videoId: string, channelId: string, { mode?: "live" | "replay" })
:
// const live = Masterchat.init(videoId);
const live = new Masterchat(videoId, channelId, { mode: "live" });
Since with this way Masterchat won't fetch metadata, should you need metadata later on, manually call populateMetadata
:
await live.populateMetadata(); // will scrape metadata from watch page
console.log(live.title);
console.log(live.channelName);
Note
Fetching watch page is rate limited
cd extra/credentials-fetcher
npm i
npm start
> [email protected] start
> electron .
Login succeeded. Use credential token below:
eyJTSUQiOiJL[omit]iJBSEwx
Set credentials.
const credentials = "eyJTSUQiOiJL[omit]iJBSEwx";
const client = await Masterchat.init(id, { credentials });
import axios from "axios";
import https from "https";
import { Masterchat } from "masterchat";
const axiosInstance = axios.create({
timeout: 4000,
httpsAgent: new https.Agent({ keepAlive: true }),
});
const mc = await Masterchat.init("<videoId>", { axiosInstance });
type |
description |
---|---|
addChatItemAction | Live chat message |
addSuperChatItemAction | Super chat message |
addSuperStickerItemAction | Super sticker message |
addMembershipItemAction | Membership joining message |
addMembershipMilestoneItemAction | Membership milestone message |
addPlaceholderItemAction | Add a placeholder for later usage (invisible) |
replaceChatItemAction | Replace a live chat or placeholder with a placeholder or live chat |
markChatItemAsDeletedAction | Delete live chat by id |
markChatItemsByAuthorAsDeletedAction | Delete live chats by authorChannelId |
addSuperChatTickerAction | Ticker for super chat |
addSuperStickerTickerAction | Ticker for super sticker |
addMembershipTickerAction | Ticker for membership joining event |
addBannerAction | Pin a message |
removeBannerAction | Remove a pinned message |
addViewerEngagementMessageAction | Viewer engagement message |
showPanelAction | Show a panel (generic) |
showPollPanelAction | Show a poll panel |
updatePollAction | Update a poll panel content |
closePanelAction | Close a panel |
addPollResultAction | Poll result |
showTooltipAction | Tooltip |
modeChangeAction | Notify mode changes (slow mode, members-only, subscribers-only) |
membershipGiftPurchaseAction | Membership gift purchase notification |
membershipGiftRedemptionAction | Membership gift redemption notification |
moderationMessageAction | Moderation message |
addIncomingRaidBannerAction | Incoming raid notification |
addOutgoingRaidBannerAction | Outgoing raid notification |
addProductBannerAction | Product promotion banner notification |
removeChatItemAction | Remove chat item action |
type | metadata.isLive | Masterchat.init() | mode: undefined | mode: "live" | mode: "replay" |
---|---|---|---|---|---|
live/pre stream | true |
OK | OK | OK | DisabledChatError |
pre stream but chat disabled | true |
DisabledChatError |
DisabledChatError |
DisabledChatError |
DisabledChatError |
archived stream | false |
OK | OK | DisabledChatError |
OK |
archived stream but replay chat being processed | false |
DisabledChatError |
DisabledChatError |
DisabledChatError |
DisabledChatError |
members-only live stream | N/A | MembersOnlyError |
DisabledChatError |
MembersOnlyError |
DisabledChatError |
members-only archived stream | N/A | MembersOnlyError |
OK | DisabledChatError |
OK |
unarchived stream | N/A | NoStreamRecordingError |
DisabledChatError |
DisabledChatError |
DisabledChatError |
privated stream | N/A | NoPermissionError |
NoPermissionError |
NoPermissionError |
NoPermissionError |
deleted stream | N/A | UnavailableError |
UnavailableError |
UnavailableError |
UnavailableError |
invalid video/channel id | N/A | InvalidArgumentError |
InvalidArgumentError |
InvalidArgumentError |
InvalidArgumentError |