Skip to content

Latest commit

 

History

History
298 lines (230 loc) · 14 KB

MANUAL.md

File metadata and controls

298 lines (230 loc) · 14 KB

Masterchat Manual

Usage

Just grab metadata

import { Masterchat, stringify } from "masterchat";

const { title, channelId, channelName } = await Masterchat.init("<videoId>");

console.log(`info: ${title} @ ${channelName} (${channelId})`);

Iterate over live chats

Event Emitter API

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();

Async Iterator API

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");

Save replay chats in .jsonl

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

Chat moderation bot

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);
  }
}

Get video comments (≠ live chats)

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);

Get transcript

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));
}

Advanced usage

Faster instantiation

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

Fetch credentials

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 });

Custom axios client

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 });

Reference

API Documentation

Action type

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

Stream type

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