diff --git a/.eslintrc b/.eslintrc index e07ebbd67..eb8442bd9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,9 +13,12 @@ "rules": { "no-console": "warn", - "@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/consistent-type-definitions": ["error", "type"], "@typescript-eslint/no-explicit-any": ["off"], + "@typescript-eslint/no-unused-vars": [ + "error", + { "ignoreRestSiblings": true } + ], "require-await": "warn" }, diff --git a/src/cache/tip-fact-cache.ts b/src/cache/tip-fact-cache.ts index 428f1a8e7..d6115f9e6 100644 --- a/src/cache/tip-fact-cache.ts +++ b/src/cache/tip-fact-cache.ts @@ -1,7 +1,9 @@ import config from "adapters/config" import { TEST } from "env" +import { logger } from "logger" import NodeCache from "node-cache" import { getChance } from "utils/common" +import retry from "retry" const contentCache = new NodeCache({ stdTTL: 3600, @@ -9,15 +11,21 @@ const contentCache = new NodeCache({ useClones: false, }) -export async function getTipsAndFacts() { - const { ok, data } = await config.getContent("header") - - if (ok) { - contentCache.set("content", data) - } else { - contentCache.set("content", { description: { fact: [], tip: [] } }) - getTipsAndFacts() - } +export function getTipsAndFacts() { + const operation = retry.operation() + operation.attempt((currentAttempt) => { + logger.info(`Fetching tips and facts... (attempt ${currentAttempt})`) + config.getContent("header").then(({ ok, data }) => { + if (ok) { + logger.info("Fetch tips and facts OK") + contentCache.set("content", data) + } else { + logger.warn("Fetch tips and facts FAIL, retrying...") + contentCache.set("content", { description: { fact: [], tip: [] } }) + operation.retry() + } + }) + }) } export function getRandomFact() { diff --git a/src/commands/watchlist/view/chart.ts b/src/commands/watchlist/view/chart.ts index 82c487a41..3f32f9fcf 100644 --- a/src/commands/watchlist/view/chart.ts +++ b/src/commands/watchlist/view/chart.ts @@ -5,7 +5,7 @@ import { heightOf, widthOf } from "ui/canvas/calculator" import { renderChartImage } from "ui/canvas/chart" import { drawCircleImage, drawRectangle } from "ui/canvas/draw" import { loadAndCacheImage } from "ui/canvas/image" -import { EmojiKey, emojis, getEmojiURL } from "utils/common" +import { emojis, getEmojiURL } from "utils/common" export async function renderChart(data: any[]) { const container: RectangleStats = { @@ -73,7 +73,7 @@ export async function renderChart(data: any[]) { if (!imageUrl && is_pair) { const [base, target] = symbol .split("/") - .map((s: string) => emojis[s.toUpperCase() as EmojiKey]) + .map((s: string) => emojis[s.toUpperCase() as keyof typeof emojis]) imageUrl = base && target ? [getEmojiURL(base), getEmojiURL(target)].join("||") diff --git a/src/errors/command-argument.ts b/src/errors/command-argument.ts index d53c02ad3..005ad90b2 100644 --- a/src/errors/command-argument.ts +++ b/src/errors/command-argument.ts @@ -26,8 +26,7 @@ export class CommandArgumentError extends BotBaseError { if ((embeds?.length ?? 0) > 0) { this.reply?.({ content: `> It may be incorrect command, here's a help reference for you ${getEmoji( - "ANIMATED_POINTING_DOWN", - true + "ANIMATED_POINTING_DOWN" )}`, embeds, }) diff --git a/src/errors/command-not-allowed.ts b/src/errors/command-not-allowed.ts index 407e8d155..0c6ccd311 100644 --- a/src/errors/command-not-allowed.ts +++ b/src/errors/command-not-allowed.ts @@ -50,8 +50,7 @@ export class CommandNotAllowedToRunError extends BotBaseError { .join("\n")}` : "" }\n\n${getEmoji( - "ANIMATED_POINTING_RIGHT", - true + "ANIMATED_POINTING_RIGHT" )} Contact this server's owner to use ${await getSlashCommand( "bot-manager set" )} to add your role as a bot manager role.`, diff --git a/src/index.ts b/src/index.ts index 2d14b2923..67244c6b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,9 +55,7 @@ const rest = new REST({ version: "9" }).setToken(DISCORD_TOKEN) }) logger.info("Successfully reloaded application (/) commands.") - logger.info("Getting tips and facts.") - await getTipsAndFacts() - logger.info("Success getting tips and facts.") + getTipsAndFacts() runHttpServer() } catch (error) { diff --git a/src/ui/discord/embed.ts b/src/ui/discord/embed.ts index 71713f4a0..c89bb09f7 100644 --- a/src/ui/discord/embed.ts +++ b/src/ui/discord/embed.ts @@ -23,10 +23,8 @@ import { specificHelpCommand, } from "utils/commands" import { - TokenEmojiKey, emojis, getEmoji, - getEmojiToken, getEmojiURL, msgColors, roundFloatNumber, @@ -642,10 +640,10 @@ export function composeInsufficientBalanceEmbed({ }: { current?: number required?: number - symbol: TokenEmojiKey + symbol: string author?: User }) { - const tokenEmoji = getEmojiToken(symbol) + const tokenEmoji = getEmoji(symbol) return composeEmbedMessage(null, { author: ["Insufficient balance", getEmojiURL(emojis.REVOKE)], description: `${author}, your balance is insufficient.\nYou can deposit more by using `, diff --git a/src/ui/discord/select-menu.ts b/src/ui/discord/select-menu.ts index fb0408617..da31594c0 100644 --- a/src/ui/discord/select-menu.ts +++ b/src/ui/discord/select-menu.ts @@ -10,13 +10,7 @@ import { } from "discord.js" import { SetDefaultButtonHandler, SetDefaultRenderList } from "types/common" import { InteractionHandler } from "handlers/discord/select-menu" -import { - EmojiKey, - getAuthor, - getDateStr, - getEmoji, - hasAdministrator, -} from "utils/common" +import { getAuthor, getDateStr, getEmoji, hasAdministrator } from "utils/common" import { VERTICAL_BAR } from "utils/constants" import { composeEmbedMessage } from "./embed" import dayjs from "dayjs" @@ -104,7 +98,7 @@ export function composeSimpleSelection( .map((o, i) => customRender ? customRender(o, i) - : `${getEmoji(`NUM_${i + 1}` as EmojiKey)}${VERTICAL_BAR}${o}` + : `${getEmoji(`NUM_${i + 1}`)}${VERTICAL_BAR}${o}` ) .join("\n")}` } diff --git a/src/utils/common.ts b/src/utils/common.ts index a94c0455a..8fc7723a3 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -26,7 +26,7 @@ import { } from "@bonfida/spl-name-service" import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js" -import { DOT, SPACE } from "./constants" +import { API_BASE_URL, DOT, SPACE } from "./constants" import { marketplaceEmojis, rarityEmojis, @@ -35,6 +35,68 @@ import { } from "./nft" import { swr } from "adapters/fetcher" +class EmojiCache { + private cache: Map + public fallback: string + + constructor() { + this.cache = new Map() + this.fallback = "" + this.fetch() + } + + async fetch() { + try { + logger.info("Fetching emoji data...") + const res = await fetch(`${API_BASE_URL}/product-metadata/emoji`) + if (!res.ok) throw new Error("") + const json = await res.json().catch(() => null) + if (!json) throw new Error("") + const { data } = json + const emojis = data as any[] + for (const emoji of emojis) { + this.cache.set(emoji.code.toUpperCase(), { + emoji: emoji.emoji, + url: emoji.emoji_url, + }) + } + logger.info("Fetch emoji data OK") + } catch (e: any) { + logger.error(e) + logger.error("Fetch emoji data FAIL") + process.exit(1) + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getEmoji(_code: string, ..._args: any[]) { + const code = _code.toUpperCase() + if (!this.cache.has(code)) return this.fallback + + return this.cache.get(code)!.emoji + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getEmojiURL(_code: string, ..._args: any[]) { + const code = _code.toUpperCase() + if (!this.cache.has(code)) return this.fallback + + return this.cache.get(code)!.url + } + + getRawEmojiURL(id: string) { + return `https://cdn.discordapp.com/emojis/${id}.png?size=240&quality=lossless` + } +} + +const Emoji = new EmojiCache() + +export const getEmoji = Emoji.getEmoji.bind(Emoji) +export const getEmojiToken = Emoji.getEmoji.bind(Emoji) +export const getEmojiURL = Emoji.getRawEmojiURL.bind(Emoji) +export type EmojiKey = string +export type TokenEmojiKey = string + dayjs.extend(relativeTime) const SOL_TLD_AUTHORITY = new PublicKey( @@ -507,9 +569,6 @@ export const thumbnails = { "https://cdn.discordapp.com/attachments/933195103273627719/1100350433295339541/rocket.webp", } -export type EmojiKey = keyof typeof emojis -export type TokenEmojiKey = keyof typeof tokenEmojis - export function isInteraction( msgOrInteraction: Message | MessageComponentInteraction ): msgOrInteraction is MessageComponentInteraction { @@ -546,31 +605,6 @@ export function maskAddress(str: string, minLen?: number) { return str } -export function getEmoji( - key: EmojiKey | "", - animated?: boolean, - fallback = "" -) { - if (!key) return fallback - - const emoji = emojis[key.toUpperCase() as EmojiKey] - if (!emoji) return fallback - - if (isNaN(+emoji)) { - return emoji - } - - return `<${ - animated || key.toUpperCase().startsWith("ANIMATED_") ? "a" : "" - }:${key.toUpperCase().replace(/-/g, "_").toLowerCase()}:${ - emojis[key.toUpperCase() as EmojiKey] - }>` -} - -export function getEmojiToken(key: TokenEmojiKey, animated?: boolean) { - return getEmoji(key, animated, getEmoji("ANIMATED_COIN_1", true)) -} - export function roundFloatNumber(n: number, fractionDigits = 1) { return parseFloat(parseFloat(`${n}`).toFixed(fractionDigits)) } @@ -579,10 +613,6 @@ export function capFirst(str = "") { return `${str.charAt(0).toUpperCase()}${str.slice(1)}` } -export function getEmojiURL(emojiId: string) { - return `https://cdn.discordapp.com/emojis/${emojiId}.png?size=240&quality=lossless` -} - export function getAnimatedEmojiURL(emojiId: string) { return `https://cdn.discordapp.com/emojis/${emojiId}.gif?size=240&quality=lossless` } diff --git a/src/utils/defi.ts b/src/utils/defi.ts index 82419c896..7fce094dc 100644 --- a/src/utils/defi.ts +++ b/src/utils/defi.ts @@ -2,7 +2,7 @@ import { CommandInteraction, Message } from "discord.js" import { APIError } from "errors" import { InsufficientBalanceError } from "errors/insufficient-balance" import mochiPay from "../adapters/mochi-pay" -import { getAuthor, TokenEmojiKey } from "./common" +import { getAuthor } from "./common" import { convertString } from "./convert" import { getProfileIdByDiscord } from "./profile" import { COMMA, EMPTY } from "./constants" @@ -101,7 +101,7 @@ export async function validateBalance({ all, }: { msgOrInteraction: Message | CommandInteraction - token: TokenEmojiKey + token: string amount: number all?: boolean }) { diff --git a/src/utils/emoji.ts b/src/utils/emoji.ts index 09666dcb7..35a7bf71e 100644 --- a/src/utils/emoji.ts +++ b/src/utils/emoji.ts @@ -26,8 +26,7 @@ export function throwOnInvalidEmoji(emoji: string, msg: OriginalMessage) { msgOrInteraction: msg, title: "Unsupported emojis", description: `${getEmoji( - "ANIMATED_POINTING_RIGHT", - true + "ANIMATED_POINTING_RIGHT" )} Please use an emoji from this server or in the Discord default list.`, }) }