Skip to content

Latest commit

 

History

History
297 lines (228 loc) · 13.1 KB

MANUAL.md

File metadata and controls

297 lines (228 loc) · 13.1 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 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);
  }
}

Get video comments (≠ live chats)

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

Get transcript

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

Advanced usage

Faster instantiation

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

Fetch credentials

cd extra/credentials-fetcher
npm i
npm start

> credential-fetcher@0.0.0 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
addRedirectBannerAction Redirect banner notification (raid event)

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