Skip to content

Commit

Permalink
feat: add support for membership gift
Browse files Browse the repository at this point in the history
  • Loading branch information
uetchy committed Mar 4, 2022
1 parent b1d4d15 commit d619330
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 15 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

### New

- New action: `MembershipGiftPurchaseAction` and `MembershipGiftRedemptionAction`
- New action: `AddPollResultAction` (separated from `AddViewerEngagementMessageAction`)
- New: `getComment` for fetching video comment by id
- New params for `AddBannerAction`: `viewerIsCreator`, `targetId`
- New API `getComments` for fetching video comments
- Support for Video Comment API:
- `getComments` for fetching video comments
- `getComment` for fetching video comment by id

### Improvements

Expand Down
99 changes: 99 additions & 0 deletions src/chat/actions/addChatItemAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
LiveChatMode,
ModeChangeAction,
AddPollResultAction,
MembershipGiftPurchaseAction,
MembershipGiftRedemptionAction,
} from "../../interfaces/actions";
import {
YTAddChatItemAction,
Expand All @@ -17,6 +19,8 @@ import {
YTLiveChatPaidMessageRenderer,
YTLiveChatPaidStickerRenderer,
YTLiveChatPlaceholderItemRenderer,
YTLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer,
YTLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer,
YTLiveChatTextMessageRenderer,
YTLiveChatViewerEngagementMessageRenderer,
YTRunContainer,
Expand Down Expand Up @@ -58,6 +62,18 @@ export function parseAddChatItemAction(payload: YTAddChatItemAction) {
// Mode change message (e.g. toggle members-only)
const renderer = item["liveChatModeChangeMessageRenderer"]!;
return parseLiveChatModeChangeMessageRenderer(renderer);
} else if ("liveChatSponsorshipsGiftPurchaseAnnouncementRenderer" in item) {
// Sponsorships gift purchase announcement
const renderer =
item["liveChatSponsorshipsGiftPurchaseAnnouncementRenderer"];
return parseLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer(renderer);
} else if ("liveChatSponsorshipsGiftRedemptionAnnouncementRenderer" in item) {
// Sponsorships gift purchase announcement
const renderer =
item["liveChatSponsorshipsGiftRedemptionAnnouncementRenderer"];
return parseLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer(
renderer
);
}

debugLog(
Expand Down Expand Up @@ -397,3 +413,86 @@ export function parseLiveChatModeChangeMessageRenderer(
};
return parsed;
}

// Sponsorships gift purchase announcement
export function parseLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer(
renderer: YTLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer
) {
const id = renderer.id;
const timestampUsec = renderer.timestampUsec;
const timestamp = tsToDate(timestampUsec);
const authorChannelId = renderer.authorExternalChannelId;

const header = renderer.header.liveChatSponsorshipsHeaderRenderer;
const authorName = stringify(header.authorName);
const authorPhoto = pickThumbUrl(header.authorPhoto);
const channelName = header.primaryText.runs[3].text;
const amount = parseInt(header.primaryText.runs[1].text, 10);
const image = header.image.thumbnails[0].url;

if (!authorName) {
debugLog(
"[action required] empty authorName while parsing membershipGiftPurchaseAction",
JSON.stringify(renderer)
);
}

const membership = parseMembership(
header.authorBadges[header.authorBadges.length - 1]
)!;

if (!membership) {
debugLog(
"[action required] empty membership while parsing membershipGiftPurchaseAction",
JSON.stringify(renderer)
);
}

const parsed: MembershipGiftPurchaseAction = {
type: "membershipGiftPurchaseAction",
id,
timestamp,
timestampUsec,
channelName,
amount,
membership,
authorName,
authorChannelId,
authorPhoto,
image,
};
return parsed;
}

// Sponsorships gift redemption announcement
export function parseLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer(
renderer: YTLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer
) {
const id = renderer.id;
const timestampUsec = renderer.timestampUsec;
const timestamp = tsToDate(timestampUsec);
const authorChannelId = renderer.authorExternalChannelId;

const authorName = stringify(renderer.authorName);
const authorPhoto = pickThumbUrl(renderer.authorPhoto);
const senderName = renderer.message.runs[1].text;

if (!authorName) {
debugLog(
"[action required] empty authorName while parsing membershipGiftPurchaseAction",
JSON.stringify(renderer)
);
}

const parsed: MembershipGiftRedemptionAction = {
type: "membershipGiftRedemptionAction",
id,
timestamp,
timestampUsec,
senderName,
authorName,
authorChannelId,
authorPhoto,
};
return parsed;
}
29 changes: 28 additions & 1 deletion src/interfaces/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export type Action =
| UpdatePollAction
| AddPollResultAction
| ShowTooltipAction
| ModeChangeAction;
| ModeChangeAction
| MembershipGiftPurchaseAction
| MembershipGiftRedemptionAction;

export interface AddChatItemAction {
type: "addChatItemAction";
Expand Down Expand Up @@ -329,6 +331,31 @@ export interface ModeChangeAction {
description: string;
}

export interface MembershipGiftPurchaseAction {
type: "membershipGiftPurchaseAction";
id: string;
timestamp: Date;
timestampUsec: string;
channelName: string; // MEMO: is it limited for ¥500 membership?
amount: number; // 5, 10, 20
membership: Membership;
authorName: string;
authorChannelId: string;
authorPhoto: string;
image: string; // always https://www.gstatic.com/youtube/img/sponsorships/sponsorships_gift_purchase_announcement_artwork.png
}

export interface MembershipGiftRedemptionAction {
type: "membershipGiftRedemptionAction";
id: string;
timestamp: Date;
timestampUsec: string;
senderName: string; // author was gifted a membership by sender
authorName: string;
authorChannelId: string;
authorPhoto: string;
}

export interface UnknownAction {
type: "unknown";
payload: unknown;
Expand Down
81 changes: 69 additions & 12 deletions src/interfaces/yt/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export interface YTLiveChatNavigationEndpointContainer
clickTrackingParams: string;
showLiveChatParticipantsEndpoint?: YTSEndpoint;
toggleLiveChatTimestampsEndpoint?: YTSEndpoint;
popoutLiveChatEndpoint?: YTPopoutLiveChatEndpoint;
popoutLiveChatEndpoint?: YTThumbnailWithoutSize;
feedbackEndpoint?: YTFeedbackEndpoint;
confirmDialogEndpoint?: YTConfirmDialogEndpoint;
}
Expand Down Expand Up @@ -252,7 +252,9 @@ export type YTAddChatItemActionItem =
| YTLiveChatMembershipItemRendererContainer
| YTLiveChatPlaceholderItemRendererContainer
| YTLiveChatViewerEngagementMessageRendererContainer
| YTLiveChatModeChangeMessageRendererContainer;
| YTLiveChatModeChangeMessageRendererContainer
| YTLiveChatSponsorshipsGiftPurchaseAnnouncementRendererContainer
| YTLiveChatSponsorshipsGiftRedemptionAnnouncementRendererContainer;

export interface YTAddLiveChatTickerItemAction {
item: YTAddLiveChatTickerItem;
Expand Down Expand Up @@ -340,10 +342,19 @@ export interface YTLiveChatModeChangeMessageRendererContainer {
liveChatModeChangeMessageRenderer: YTLiveChatModeChangeMessageRenderer;
}

// Only appeared in YTShowLiveChatActionPanelAction
export interface YTLiveChatActionPanelRendererContainer {
liveChatActionPanelRenderer: YTLiveChatActionPanelRenderer;
}

export interface YTLiveChatSponsorshipsGiftPurchaseAnnouncementRendererContainer {
liveChatSponsorshipsGiftPurchaseAnnouncementRenderer: YTLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer;
}

export interface YTLiveChatSponsorshipsGiftRedemptionAnnouncementRendererContainer {
liveChatSponsorshipsGiftRedemptionAnnouncementRenderer: YTLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer;
}

// LiveChat Renderers

export interface YTLiveChatTextMessageRenderer {
Expand Down Expand Up @@ -534,6 +545,52 @@ export interface YTLiveChatModeChangeMessageRenderer {
subtext: YTText;
}

// Sponsorships gift purchase announcement
export interface YTLiveChatSponsorshipsGiftPurchaseAnnouncementRenderer {
id: string;
timestampUsec: string;
authorExternalChannelId: string;
header: {
liveChatSponsorshipsHeaderRenderer: YTLiveChatSponsorshipsHeaderRenderer;
};
}

export interface YTLiveChatSponsorshipsHeaderRenderer {
authorName: YTSimpleTextContainer;
authorPhoto: YTThumbnailList;
primaryText: {
runs: [
{ text: "Gifted "; bold: true },
{ text: string; bold: true }, // text: "5"
{ text: " "; bold: true },
{ text: string; bold: true }, // text: "Miko Ch. さくらみこ"
{ text: " memberships"; bold: true }
];
};
authorBadges: YTLiveChatAuthorBadgeRendererContainer[];
contextMenuEndpoint: YTLiveChatItemContextMenuEndpointContainer;
contextMenuAccessibility: YTAccessibilityData;
image: YTThumbnailListWithoutSize; // https://www.gstatic.com/youtube/img/sponsorships/sponsorships_gift_purchase_announcement_artwork.png
}

// Sponsorships gift redemption announcement
export interface YTLiveChatSponsorshipsGiftRedemptionAnnouncementRenderer {
id: string;
timestampUsec: string;
authorExternalChannelId: string;
authorName: YTSimpleTextContainer;
authorPhoto: YTThumbnailList;
message: {
runs: [
{ text: "was gifted a membership by "; italics: true },
{ text: string; bold: true; italics: true } // text: "User"
];
};
contextMenuEndpoint: YTLiveChatItemContextMenuEndpointContainer;
contextMenuAccessibility: YTAccessibilityData;
trackingParams: string;
}

// Ticker Renderers

export interface YTAddLiveChatTickerItem {
Expand Down Expand Up @@ -635,21 +692,13 @@ export interface YTInteractionMessage {

export interface YTAuthorBadge {
liveChatAuthorBadgeRenderer: {
customThumbnail?: YTCustomThumbnail;
customThumbnail?: YTThumbnailListWithoutSize;
icon?: YTIcon;
tooltip: string;
accessibility: YTAccessibilityData;
};
}

export interface YTCustomThumbnail {
thumbnails: YTPopoutLiveChatEndpoint[];
}

export interface YTPopoutLiveChatEndpoint {
url: string;
}

export interface YTSendLiveChatMessageEndpoint {
sendLiveChatMessageEndpoint: YTEndpointParamsContainer;
}
Expand Down Expand Up @@ -707,7 +756,7 @@ export interface YTApiEndpointMetadata {

export interface YTPopoutLiveChatEndpointContainer {
clickTrackingParams: string;
popoutLiveChatEndpoint: YTPopoutLiveChatEndpoint;
popoutLiveChatEndpoint: YTThumbnailWithoutSize;
}

export enum YTWebPageType {
Expand Down Expand Up @@ -779,12 +828,20 @@ export interface YTThumbnailList {
accessibility?: YTAccessibilityData;
}

export interface YTThumbnailListWithoutSize {
thumbnails: YTThumbnailWithoutSize[];
}

export interface YTThumbnail {
url: string;
width: number;
height: number;
}

export interface YTThumbnailWithoutSize {
url: string;
}

export interface YTLiveChatBannerRendererHeader {
liveChatBannerHeaderRenderer: {
icon: YTIcon;
Expand Down

0 comments on commit d619330

Please sign in to comment.