Skip to content

Commit

Permalink
Move Description to an embed created by a button
Browse files Browse the repository at this point in the history
  • Loading branch information
danthonywalker committed Aug 15, 2023
1 parent 122b6e4 commit 50739a7
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const CacheKey = {
channel: (channelId: string) => `${keyPrefix}:channel:${channelId}`,
channels: (query: string) => `${keyPrefix}:channels:${normalizeInput(query)}`,
videos: (playlistId: string) => `${keyPrefix}:videos:${playlistId}`,
video: (videoId: string) => `${keyPrefix}:video:${videoId}`,
} as const;
// endregion

Expand Down
116 changes: 99 additions & 17 deletions src/features/creators/post/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ import PlaylistItemSnippet = youtube_v3.Schema$PlaylistItemSnippet;

import type { Guild } from "discord.js";
import {
bold,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelType,
DiscordAPIError,
EmbedBuilder,
Events,
roleMention,
} from "discord.js";
import { compress } from "compress-tag";
import loggerFactory from "pino";

import discord from "../../../services/discord";
import { getThumbnailUrl, getVideoUrl } from "../../../services/youtube";
import discord, { addField, Color } from "../../../services/discord";
import {
getChannelUrl,
getThumbnailUrl,
getVideoUrl,
} from "../../../services/youtube";
import sleep from "../../../sleep";

import type { CreatorSubscription } from "./database";
Expand All @@ -22,6 +28,9 @@ import * as creatorsDatabase from "../database";
import * as youtube from "../youtube";
import { CreatorType } from "../constants";

const DESCRIPTION_BUTTON_ID_PREFIX = // DO NOT CHANGE
"GLOBAL_8f75c929-77a9-469c-8662-fec4a8c26a95_";

const logger = loggerFactory({
name: __filename,
});
Expand Down Expand Up @@ -72,8 +81,8 @@ const postFromYouTube = async (creatorSubscription: CreatorSubscription) => {
const { title: channelName, thumbnails } = snippet ?? {};
if (typeof channelName !== "string") return;

const post = async (video: PlaylistItemSnippet = {}) => {
const { description, publishedAt, resourceId, title } = video;
const post = async (video: PlaylistItemSnippet) => {
const { publishedAt, resourceId, title } = video;
const { videoId } = resourceId ?? {};

// prettier-ignore
Expand All @@ -84,28 +93,30 @@ const postFromYouTube = async (creatorSubscription: CreatorSubscription) => {
if (videoDate < createdAt) return false;

const videoUrl = getVideoUrl(videoId);
const header =
const content =
creatorMentionRoleId === null
? videoUrl
: `${roleMention(creatorMentionRoleId)}\n${videoUrl}`;

const rawContent = compress`
${header}
\n\n${bold("Title")}
\n${title}
\n\n${bold("Description")}
\n${description ?? ""}
`;

const content = rawContent.substring(0, 2000);
const threadId = creatorParentId === null ? undefined : creatorChannelId;
const threadName = `${channelName} - ${title}`.substring(0, 100);

const webhookThreadName =
creatorChannelType === ChannelType.GuildForum ? threadName : undefined;

const button = new ButtonBuilder()
.setEmoji("📰")
.setLabel("| Click Here for Description |")
.setStyle(ButtonStyle.Primary)
.setCustomId(`${DESCRIPTION_BUTTON_ID_PREFIX}${videoId}`);

const buttonActionRow =
// prettier-ignore
new ActionRowBuilder<ButtonBuilder>()
.addComponents(button);

const message = await webhook.send({
avatarURL: getThumbnailUrl(thumbnails),
components: [buttonActionRow],
content,
username: channelName,
threadId,
Expand Down Expand Up @@ -176,3 +187,74 @@ discord.once(Events.ClientReady, async (client) => {
await Promise.all(promises);
}
});

discord.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isButton()) return;

const { customId } = interaction;
if (!customId.startsWith(DESCRIPTION_BUTTON_ID_PREFIX)) return;

const videoId = customId.slice(DESCRIPTION_BUTTON_ID_PREFIX.length);
const video = await youtube.getVideo(videoId);
const { snippet: videoSnippet, statistics } = video;

const { channelId, description, publishedAt, tags, title } =
videoSnippet ?? {};

if (typeof channelId !== "string") throw new Error(videoId);
const { snippet: channelSnippet } = await youtube.getChannel(channelId);
const { title: channelName, thumbnails } = channelSnippet ?? {};

const channelUrl = getChannelUrl(channelId);
const channelThumbnailUrl = getThumbnailUrl(thumbnails);
const videoUrl = getVideoUrl(videoId);

const author =
typeof channelName === "string"
? {
iconURL: channelThumbnailUrl,
name: channelName,
url: channelUrl,
}
: null;

const footer =
typeof title === "string"
? {
iconURL: channelThumbnailUrl,
text: title,
}
: null;

const timestamp =
typeof publishedAt === "string" ? new Date(publishedAt) : null;

let embed = new EmbedBuilder()
.setAuthor(author)
.setColor(Color.INFORMATIONAL)
.setDescription(description ?? null)
.setFooter(footer)
.setTimestamp(timestamp)
.setTitle(title ?? null)
.setURL(videoUrl);

const tagsValue = tags?.reduce((tagsValue, tag) => {
if (tagsValue === "") return `#${tag}`;
const newTagsValue = `${tagsValue} #${tag}`;
return newTagsValue.length > 6000 - embed.length ? tagsValue : newTagsValue;
}, "");

embed = addField(embed, "Views", statistics?.viewCount, true);
embed = addField(embed, "Likes", statistics?.likeCount, true);
embed = addField(embed, "Dislikes", statistics?.dislikeCount, true);
embed = addField(embed, "Comments", statistics?.commentCount, true);
embed = addField(embed, "Favorites", statistics?.favoriteCount, true);
embed = addField(embed, "Tags", tagsValue, false);

if (embed.length > 6000) embed = embed.setDescription(null);

await interaction.reply({
embeds: [embed],
ephemeral: true,
});
});
25 changes: 23 additions & 2 deletions src/features/creators/youtube.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import youtube from "../../services/youtube";
import cache, { CacheKey } from "../../cache";

const { channels, playlistItems, search } = youtube;
const { channels, playlistItems, search, videos } = youtube;
const EXPIRE_IN_30_DAYS = 2_592_000_000;
const EXPIRE_IN_30_MINUTES = 1_800_000;

Expand Down Expand Up @@ -53,7 +53,7 @@ export const getVideos = (playlistId: string) => {
const key = CacheKey.videos(playlistId);
const callback = async () => {
const { data } = await playlistItems.list({
fields: "items(snippet(description,publishedAt,resourceId,title))",
fields: "items(snippet(publishedAt,resourceId,title))",
maxResults: 50,
part: ["snippet"],
playlistId,
Expand All @@ -70,3 +70,24 @@ export const getVideos = (playlistId: string) => {

return cache.computeIfAbsent(key, callback, EXPIRE_IN_30_MINUTES);
};

export const getVideo = (videoId: string) => {
const key = CacheKey.video(videoId);
const callback = async () => {
const { data } = await videos.list({
fields:
"items(snippet(channelId,description,publishedAt,tags,title),statistics)",
id: [videoId],
maxResults: 1,
part: ["snippet, statistics"],
});

const items = data.items ?? [];
const item = items[0];

if (item !== undefined) return item;
throw new Error(videoId);
};

return cache.computeIfAbsent(key, callback, EXPIRE_IN_30_MINUTES);
};
12 changes: 11 additions & 1 deletion src/services/discord/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JSONEncodable } from "discord.js";
import type { EmbedBuilder, JSONEncodable } from "discord.js";
import { Events, Client } from "discord.js";
import loggerFactory from "pino";

Expand All @@ -20,6 +20,16 @@ export class JsonError<T> extends Error {
}
}

export const addField = (
embed: EmbedBuilder,
name: string,
value: string | null | undefined,
inline?: boolean,
) => {
if (typeof value !== "string") return embed;
return embed.addFields({ name, value, inline });
};

const discord = new Client({
intents: ["Guilds"],
});
Expand Down

0 comments on commit 50739a7

Please sign in to comment.