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 fileimport { Masterchat, stringify } from "masterchat";
import { isSpam } from "spamreaper";
// `credentials` is an object containing YouTube session cookie or a base64-encoded JSON string of them
const credentials = {
SAPISID: "<value>",
APISID: "<value>",
HSID: "<value>",
SID: "<value>",
SSID: "<value>",
};
const mc = await Masterchat.init("<videoId>", { credentials });
const iter = mc.iter().filter((action) => action.type === "addChatItemAction");
for await (const chat of iter) {
const message = stringify(chat.message, {
// omit emojis
emojiHandler: (emoji) => "",
});
if (isSpam(message) || /UGLY/.test(message)) {
// delete chat
// if flagged as spam by Spamreaper
// or contains "UGLY"
await mc.remove(action.id);
}
}import { Masterchat } from "masterchat";
const mc = new Masterchat("<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 = new Masterchat("<videoId>", "");
const transcript = await mc.getTranscript();
for (const item of transcript) {
console.log(item.startMs, stringify(item.snippet));
}To skip loading watch page, use new Masterchat(videoId: string, channelId: string, { mode?: "live" | "replay" }):
const live = new Masterchat(videoId, channelId, { mode: "live" });instead of:
const live = await Masterchat.init(videoId);The former won't fetch metadata. If you need metadata, call:
await live.populateMetadata(); // will scrape metadata from watch page
console.log(live.title);
console.log(live.channelName);cd extra/credentials-fetcher
npm i
npm start
> credential-fetcher@0.0.0 start
> electron .
Login succeeded. Use credential token below:
eyJTSUQiOiJL[omit]iJBSEwxSet 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 |
| addRedirectBannerAction | Redirect banner notification (raid event) |
| 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 |