From 10e7901c7663d12112d73fe9261868e7c7c854c4 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Mon, 16 Jan 2023 19:34:11 -0800 Subject: [PATCH 01/13] less bad temp landing page --- pages/index.tsx | 59 +++++++++++++++---------------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index 197ffd0..2962b6f 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -2,11 +2,10 @@ import { m } from "framer-motion"; import { ReactElement, useEffect, useState } from "react"; import HomeLayout from "../layouts/HomeLayout"; import { homeMain } from "../layouts/NavTemplates"; -import type { NextPageWithLayout } from "./_app"; import Image from "next/image"; import Head from "next/head"; -const Home: NextPageWithLayout = () => { +function Home() { let api7tvEmotes = `/api/7tv/emotes?c=61ad997effa9aba101bcfddf`; const [emotesUrls, setEmotes] = useState([]); const [currentEmote, setCurrentEmote] = useState(0); @@ -79,49 +78,25 @@ const Home: NextPageWithLayout = () => { Home - toffee
-
+
- -

- t -

-

ax-free

-
- -

+
+ t + off -

-

line

-
- -

- e -

-

mote

-
- -

- e -

-

xchange

-
+ + ee +
+
+ + a tax-free offline emote exchange utility + +
{
); -}; +} const sloganContainerVariants = { initial: { @@ -163,7 +138,7 @@ const sloganContainerVariants = { bounce: 0.5, stiffness: 150, delayChildren: 1.0, - staggerChildren: 0.45, + staggerChildren: 0.1, }, }, }; @@ -171,9 +146,11 @@ const sloganContainerVariants = { const sloganHeaderVariants = { initial: { opacity: 0, + y: -15, }, animate: { opacity: 1, + y: 0, }, }; From ec6f266b48a009dab16b0ea4396a8c9d8df72071 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Mon, 16 Jan 2023 19:39:51 -0800 Subject: [PATCH 02/13] why was this here --- pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/index.tsx b/pages/index.tsx index 2962b6f..83f2289 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -78,7 +78,7 @@ function Home() { Home - toffee
-
+
Date: Thu, 19 Jan 2023 19:17:49 -0800 Subject: [PATCH 03/13] redis rework/error handling --- misc/7TVAPI.tsx | 20 ++++---- misc/redis.ts | 55 ++++++++++++++++++++++ package-lock.json | 101 ++++++++++++++-------------------------- package.json | 2 +- pages/api/7tv/emotes.ts | 20 ++++++-- pages/index.tsx | 4 ++ 6 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 misc/redis.ts diff --git a/misc/7TVAPI.tsx b/misc/7TVAPI.tsx index b7e6e78..5ffebf3 100644 --- a/misc/7TVAPI.tsx +++ b/misc/7TVAPI.tsx @@ -1,8 +1,7 @@ -import Redis from "ioredis"; - -let redis = new Redis(process.env.REDIS_URL); +import type RedisInstance from "ioredis"; async function applyCache( + redis: RedisInstance, key: string, query: string, gql: boolean, @@ -11,7 +10,7 @@ async function applyCache( if (await redis.get(key)) { return JSON.parse((await redis.get(key)) as string); } else { - const response = await fetchEndpoint(query, gql); + const response = await fetchEndpoint(redis, query, gql); if (response != null) { await redis.set(key, JSON.stringify(response), "EX", cacheTime); } @@ -19,7 +18,11 @@ async function applyCache( } } -async function fetchEndpoint(query: string, gql: boolean = false) { +async function fetchEndpoint( + redis: RedisInstance, + query: string, + gql: boolean = false +) { if (await redis.get("7TV.RATE_LIMIT")) { await new Promise((resolve) => setTimeout(resolve, 1000)); } else { @@ -50,7 +53,7 @@ async function fetchGQL(query: string) { return json; } -async function getGlobalEmotes() { +async function getGlobalEmotes(redis: RedisInstance) { const gqlQuery = `query { namedEmoteSet(name: GLOBAL) { emote_count @@ -78,10 +81,10 @@ async function getGlobalEmotes() { } } }`; - return await applyCache("7TV.GLOBAL_EMOTES", gqlQuery, true, 3600); + return await applyCache(redis, "7TV.GLOBAL_EMOTES", gqlQuery, true, 3600); } -async function getChannelEmotes(channelID: string) { +async function getChannelEmotes(redis: RedisInstance, channelID: string) { const gqlQuery = `query { user(id: "${channelID}") { emote_sets { @@ -112,6 +115,7 @@ async function getChannelEmotes(channelID: string) { } }`; return await applyCache( + redis, "7TV.CHANNEL_EMOTES_" + channelID, gqlQuery, true, diff --git a/misc/redis.ts b/misc/redis.ts new file mode 100644 index 0000000..d118502 --- /dev/null +++ b/misc/redis.ts @@ -0,0 +1,55 @@ +// https://makerkit.dev/blog/tutorials/nextjs-redis (I got tired of having redis issues) +import Redis, { RedisOptions } from "ioredis"; + +const configuration = { + redis: { + host: process.env.REDIS_HOST ?? "localhost", + port: parseInt(process.env.REDIS_PORT ?? "6379"), + password: process.env.REDIS_PASSWORD ?? null, + }, +}; + +function getRedisConfiguration(): { + port: number; + host: string; + password: string | null; +} { + return configuration.redis; +} + +export function createRedisInstance(config = getRedisConfiguration()) { + try { + const options: RedisOptions = { + host: config.host, + lazyConnect: true, + showFriendlyErrorStack: true, + enableAutoPipelining: true, + maxRetriesPerRequest: 0, + retryStrategy: (times: number) => { + if (times > 3) { + throw new Error(`[Redis] Could not connect after ${times} attempts`); + } + + return Math.min(times * 200, 1000); + }, + }; + + if (config.port) { + options.port = config.port; + } + + if (config.password) { + options.password = config.password; + } + + const redis = new Redis(options); + + redis.on("error", (error: unknown) => { + console.warn("[Redis] Error connecting", error); + }); + + return redis; + } catch (e) { + throw new Error(`[Redis] Could not create a Redis instance`); + } +} diff --git a/package-lock.json b/package-lock.json index 841dc3b..0468ee6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "eslint": "8.28.0", "eslint-config-next": "13.0.4", "framer-motion": "^7.6.19", - "ioredis": "^4.28.5", + "ioredis": "^5.2.5", "next": "13.0.4", "react": "18.2.0", "react-dom": "18.2.0", @@ -122,6 +122,11 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@motionone/animation": { "version": "10.15.1", "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", @@ -1342,9 +1347,9 @@ } }, "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "engines": { "node": ">=0.10" } @@ -2515,38 +2520,28 @@ } }, "node_modules/ioredis": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", - "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.2.5.tgz", + "integrity": "sha512-7HKo/ClM2DGLRXdFq8ruS3Uuadensz4A76wPOU0adqlOqd1qkhoLPDaBhmVhUhNGpB+J65/bhLmNB8DDY99HJQ==", "dependencies": { + "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.0.1", "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" }, "engines": { - "node": ">=6" + "node": ">=12.22.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/ioredis" } }, - "node_modules/ioredis/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "engines": { - "node": ">=6" - } - }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -2841,9 +2836,9 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dependencies": { "minimist": "^1.2.0" }, @@ -3050,11 +3045,6 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -4025,11 +4015,6 @@ "node": ">=8.10.0" } }, - "node_modules/redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -5075,6 +5060,11 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "@motionone/animation": { "version": "10.15.1", "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", @@ -5868,9 +5858,9 @@ "dev": true }, "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" }, "detect-libc": { "version": "2.0.1", @@ -6728,28 +6718,19 @@ } }, "ioredis": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", - "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.2.5.tgz", + "integrity": "sha512-7HKo/ClM2DGLRXdFq8ruS3Uuadensz4A76wPOU0adqlOqd1qkhoLPDaBhmVhUhNGpB+J65/bhLmNB8DDY99HJQ==", "requires": { + "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.0.1", "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - } } }, "is-arrayish": { @@ -6940,9 +6921,9 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "requires": { "minimist": "^1.2.0" } @@ -7094,11 +7075,6 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -7736,11 +7712,6 @@ "picomatch": "^2.2.1" } }, - "redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, "redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", diff --git a/package.json b/package.json index 51afc68..dab8fe0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "eslint": "8.28.0", "eslint-config-next": "13.0.4", "framer-motion": "^7.6.19", - "ioredis": "^4.28.5", + "ioredis": "^5.2.5", "next": "13.0.4", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/pages/api/7tv/emotes.ts b/pages/api/7tv/emotes.ts index ab4694e..4718558 100644 --- a/pages/api/7tv/emotes.ts +++ b/pages/api/7tv/emotes.ts @@ -1,4 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../../misc/redis"; import { getChannelEmotes, getGlobalEmotes } from "../../../misc/7TVAPI"; type Data = { @@ -9,10 +10,19 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const channel = req.query.c - ? await getChannelEmotes(req.query.c as string) - : undefined; - const global = await getGlobalEmotes(); + const redis = createRedisInstance(); - res.status(200).json({ channel, global }); + try { + const channel = req.query.c + ? await getChannelEmotes(redis, req.query.c as string) + : undefined; + const global = await getGlobalEmotes(redis); + redis.quit(); + res.status(200).json({ channel, global }); + } catch (e) { + console.log(e); + res + .status(500) + .json({ error: { message: "7TV or internal API is down", code: 10000 } }); + } } diff --git a/pages/index.tsx b/pages/index.tsx index 83f2289..25bd22e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -14,6 +14,10 @@ function Home() { fetch(api7tvEmotes) .then((res) => res.json()) .then((data) => { + // if error, return + if (data.error) { + return; + } // get all emote URLs let emoteUrls = data.channel.user.emote_sets[0].emotes.map( (emote: any) => { From b9a892161d784c25ad30ba463bf825c1cab8756c Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Thu, 19 Jan 2023 19:23:27 -0800 Subject: [PATCH 04/13] dashboard and ranking made separate routes --- components/dashboard/NavBar.tsx | 2 +- layouts/DashLayout.tsx | 1 - pages/{dashboard/ranking.tsx => ranking/index.tsx} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename pages/{dashboard/ranking.tsx => ranking/index.tsx} (100%) diff --git a/components/dashboard/NavBar.tsx b/components/dashboard/NavBar.tsx index af25a5f..6d3277f 100644 --- a/components/dashboard/NavBar.tsx +++ b/components/dashboard/NavBar.tsx @@ -24,7 +24,7 @@ function NavBar() { variants={navIconVariants} className="pr-5 lg:pr-0 lg:pt-3 lg:pb-3" > - + diff --git a/layouts/DashLayout.tsx b/layouts/DashLayout.tsx index 9235338..addd205 100644 --- a/layouts/DashLayout.tsx +++ b/layouts/DashLayout.tsx @@ -8,7 +8,6 @@ import { import Head from "next/head"; import { useRouter } from "next/router"; import NavBar from "../components/dashboard/NavBar"; -import { NavTemplate } from "./NavTemplates"; interface DashLayoutProps { children: React.ReactNode; diff --git a/pages/dashboard/ranking.tsx b/pages/ranking/index.tsx similarity index 100% rename from pages/dashboard/ranking.tsx rename to pages/ranking/index.tsx From 6066c31319d248e2390706c4001f83e3911b82f7 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Fri, 20 Jan 2023 02:37:38 -0800 Subject: [PATCH 05/13] twitch api --- misc/TwitchAPI.tsx | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 misc/TwitchAPI.tsx diff --git a/misc/TwitchAPI.tsx b/misc/TwitchAPI.tsx new file mode 100644 index 0000000..b72a6f1 --- /dev/null +++ b/misc/TwitchAPI.tsx @@ -0,0 +1,94 @@ +import type RedisInstance from "ioredis"; + +async function applyCache( + redis: RedisInstance, + key: string, + query: string, + cacheTime: number +) { + if (await redis.get(key)) { + return JSON.parse((await redis.get(key)) as string); + } else { + const response = await fetchEndpoint(redis, query); + if (response != null) { + await redis.set(key, JSON.stringify(response), "EX", cacheTime); + } + return response; + } +} + +async function authTwitch(redis: RedisInstance) { + let auth = await redis.get("TWITCH.AUTH"); + if (auth) { + return auth; + } else { + const response = await fetch( + `https://id.twitch.tv/oauth2/token?client_id=${process.env.TWITCH_CLIENT_ID}&client_secret=${process.env.TWITCH_SECRET}&grant_type=client_credentials`, + { + method: "POST", + } + ); + const json = await response.json(); + await redis.set("TWITCH.OAUTH", json.access_token, "EX", json.expires_in); + + return json.access_token; + } +} + +async function fetchEndpoint(redis: RedisInstance, query: string) { + if (await redis.get("TWITCH.RATE_LIMIT")) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + await redis.set("TWITCH.RATE_LIMIT", "1", "EX", 1); + } + + const auth = await authTwitch(redis); + + const requestHeaders = new Headers(); + requestHeaders.append("Client-ID", process.env.TWITCH_CLIENT_ID ?? ""); + requestHeaders.append("Authorization", `Bearer ${auth}`); + + const response = await fetch(query, { + headers: requestHeaders, + }); + const json = await response.json(); + return json; +} + +async function getUserByName(redis: RedisInstance, username: string) { + return await applyCache( + redis, + "TWITCH.USER_" + username, + `https://api.twitch.tv/helix/users?login=${username}`, + 600 + ); +} + +async function getUserByID(redis: RedisInstance, userID: string) { + return await applyCache( + redis, + "TWITCH.USER_" + userID, + `https://api.twitch.tv/helix/users?id=${userID}`, + 600 + ); +} + +async function getGlobalEmotes(redis: RedisInstance) { + return await applyCache( + redis, + "TWITCH.GLOBAL_EMOTES", + `https://api.twitch.tv/helix/chat/emotes/global`, + 3600 + ); +} + +async function getChannelEmotes(redis: RedisInstance, channelID: string) { + return await applyCache( + redis, + "TWITCH.CHANNEL_EMOTES_" + channelID, + `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channelID}`, + 600 + ); +} + +export { getUserByName, getUserByID, getGlobalEmotes, getChannelEmotes }; From ef4a98cfd20876269c12288272d0c3169a8a3a5f Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Fri, 20 Jan 2023 02:38:23 -0800 Subject: [PATCH 06/13] userpage init --- pages/api/fakePrices.ts | 340 ++++++++++++++ pages/api/fakeRanking.ts | 216 --------- pages/api/fakeUsers.ts | 629 ++++++++++++++++++++++++++ pages/ranking/index.tsx | 80 ++-- pages/user/[username]/index.tsx | 391 ++++++++++++++++ public/img/well_drawn_rank_chart.webp | Bin 0 -> 14714 bytes tailwind.config.js | 5 + 7 files changed, 1408 insertions(+), 253 deletions(-) create mode 100644 pages/api/fakePrices.ts delete mode 100644 pages/api/fakeRanking.ts create mode 100644 pages/api/fakeUsers.ts create mode 100644 pages/user/[username]/index.tsx create mode 100644 public/img/well_drawn_rank_chart.webp diff --git a/pages/api/fakePrices.ts b/pages/api/fakePrices.ts new file mode 100644 index 0000000..b801d38 --- /dev/null +++ b/pages/api/fakePrices.ts @@ -0,0 +1,340 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const emote = req.query.emote ? (req.query.emote as string) : undefined; + if (!emote) { + res.status(200).json({ data: fakePrices }); + return; + } + res.status(200).json({ data: fakePrices[emote] }); +} + +const fakePrices: { [key: string]: number } = { + "3Head": 521, + "4Health": 801, + ":3": 104, + ":(": 799, + AAAA: 717, + AAAAUUUUUUGHHHHHHH: 342, + AINTNAURWAY: 195, + AREYOUAFEMBOY: 135, + AREYOUAGIRL: 494, + AYAYAjam: 907, + Adge: 646, + AlienPls3: 460, + Amonge: 608, + AngelThump: 195, + AnnyLebronJam: 967, + Aware: 571, + BAND: 472, + BASED: 794, + BEGGING: 365, + BLUBBERS: 211, + BLUBBERSWTF: 713, + BOOBEST: 107, + BOTHA: 472, + BRUHgers: 536, + Baby: 927, + Binoculars: 441, + Blindge: 426, + BocchiPossessed: 545, + Bruhgi: 779, + CEASE: 708, + COCKA: 656, + CapybaraStare: 295, + CatChest: 267, + ChadMeeting: 454, + Chatting: 330, + ChugU: 661, + Clueless: 888, + Coldge: 314, + Comfi: 187, + Copege: 841, + Corpa: 536, + CumTime: 199, + DIESOFAYAYA: 657, + DIESOFCRINGE: 320, + DONUT: 57, + Danki: 864, + Deadge: 322, + Despairge: 437, + Deutschge: 626, + EDM: 572, + FLASHBANG: 308, + FeelsWeakMan: 88, + Fishinge: 956, + Flushed: 290, + FluteTime: 557, + GOODONE: 804, + GachiPls: 415, + Gambage: 876, + Gayge: 434, + GetOutOfMyHead: 338, + GoodGirl: 761, + GroupWankge: 540, + GuysRefreshChatterinoIUploadedAnotherEmote: 677, + HACKERMANS: 85, + HUH: 582, + HYPERS: 142, + Homi: 745, + HopOnBlueArchive: 386, + IDC: 277, + ILOST: 894, + JIGACHAD: 865, + Jamgie: 156, + Jammies: 308, + Joel: 586, + KEKW: 356, + KKool: 821, + KoroneOkite: 90, + LETHIMCOOK: 940, + LICKA: 866, + LUBBERS: 808, + LULE: 244, + Lagging: 314, + LickAnnyThighs: 66, + Life: 894, + LoveForever: 21, + MODS: 180, + MYHEART: 122, + MaN: 181, + MadgeJuice: 926, + MadgeTime: 481, + Madgeclap: 534, + Malding: 940, + Maxwell: 656, + MyHonestReaction: 314, + NAUR: 93, + NGOthis: 454, + NOBOOBS: 855, + NODDERS: 836, + NOTED: 293, + Naruga: 572, + Naruge: 872, + Nerdge: 46, + NessieTwerk: 666, + OFFLINECHAT: 780, + OMEGA: 934, + OMG: 631, + "OhTheMiseryEverybodyWantsToBeMyEnemySpareTheSympathyEverybodyWantsToBeMyEnemy-y-y-y-y": 798, + Okayeg: 485, + OkaygeBusiness: 493, + Oldge: 763, + OrangexddRun: 96, + PEPELEPSY: 122, + PLEASE: 607, + PagMan: 775, + PantsGrab: 503, + Party: 181, + PeepoKittyHug: 360, + PensiveWobble: 843, + PepeClown: 384, + PepeNPC: 131, + Pepepains: 105, + PepoG: 987, + PogOanny: 14, + PogTasty: 211, + PogU: 245, + Pogpega: 902, + PokiShare: 274, + Poorge: 789, + PoroRoast: 199, + Programming: 161, + RAGEY: 322, + RAVE: 67, + RESETTING: 749, + RIDING: 364, + RIPBOZO: 638, + Ratge: 981, + SCHIZO: 457, + SHITUP: 643, + SNIFFA: 220, + SPEED: 956, + SadCat: 747, + Sadeg: 313, + SadgeCry: 869, + SaguiPls: 363, + Siti: 396, + Smadge: 222, + SmugCat: 447, + SnowTime: 545, + SoCute: 317, + Spoopy: 281, + Suske: 642, + THESE: 401, + THIS: 311, + Tasty: 530, + ThisStream: 497, + TouchGrass: 608, + TurtleRush: 608, + UOHHH: 426, + VIBE: 939, + VIBEOFF: 36, + VaN: 479, + VeryBased: 456, + ViolinTime: 406, + WHAT: 941, + WHOMEGALUL: 804, + WakuWaku: 221, + WeebRun: 582, + WhoAsked: 459, + Wickedgi: 870, + Wigglecat: 649, + Wokege: 377, + YourMom: 939, + amongnnE: 142, + annE: 552, + annie: 292, + annyBlob: 471, + annyBop: 749, + annyCD: 673, + annyCucumber: 689, + annyDespair: 736, + annyExcitedHug: 926, + annyGAMBA: 885, + annyGasm: 49, + annyGlare: 462, + annyHappy: 960, + annyHug: 979, + annyJam: 544, + annyLava: 288, + annyNODDERS: 730, + annyNOW: 68, + annyOffline: 241, + annyPadoru: 453, + annyPag: 377, + annyPoggies: 729, + annyPooPoo: 295, + annySCHIZO: 766, + annySmile: 467, + annySussy: 695, + annySwipe: 992, + annyTalk: 951, + annyThighs: 458, + annyVibe: 914, + annykiss: 308, + annysilly: 996, + annystare: 34, + annytfCum: 27, + anyaPls: 322, + anyatf: 58, + baseg: 551, + borpaSpin: 793, + burh: 716, + catKISS: 88, + catRAVE: 912, + catbaby: 673, + chatlookwhatannytaughtme: 515, + danse: 213, + deadass: 815, + degen: 551, + dejj: 843, + donowall: 660, + dvaAss: 871, + elmoFire: 117, + ewLeague: 240, + ewOverwatch: 397, + forsen: 482, + forsenGa: 327, + forsenGaPick: 149, + forsenLaughingAtYou: 292, + frenn: 575, + golive: 103, + guraFukkireta: 767, + guraWiggle: 132, + guraWink: 891, + happ: 525, + heCrazy: 437, + hiThere: 54, + hmmMeeting: 749, + iAsked: 203, + jupijej: 207, + kiss0: 738, + kok: 313, + koklick: 990, + koroneWink: 824, + lebronJAM: 890, + liccanny: 205, + lobaPls: 436, + majj: 813, + meow: 757, + monkaE: 376, + monkaLaugh: 335, + nekoNya: 948, + nessieWalk: 672, + nise: 658, + nyanPls: 330, + pL: 38, + paapoHappy: 126, + peepoCat: 673, + peepoClap: 772, + peepoFAT: 408, + peepoFeet: 909, + peepoFinger: 185, + peepoFlower: 569, + peepoFlute: 361, + peepoHeadbang: 615, + peepoHigh: 125, + peepoLeaveToAnny: 711, + peepoPag: 101, + peepoPopcorn: 234, + peepoRant: 628, + peepoRiot: 874, + peepoSnow: 90, + peepoStuck: 188, + peepoTalk: 452, + pepeKneel: 87, + pepePoint: 641, + pepegaJAMMER: 603, + plink: 892, + poggcrazy: 319, + pogs: 829, + poroPls: 87, + ppHop: 589, + ppHopper: 843, + ppPoof: 188, + sadWankge: 300, + sajj: 44, + sitt: 450, + soncic: 483, + sonic: 464, + squirrelJAM: 431, + toffee1: 591, + toffee2: 51, + toffee3: 3, + toffeeBlankies: 249, + toffeeBop: 801, + toffeeChat: 405, + toffeeClap: 552, + toffeeComfy: 383, + toffeeConfused: 548, + toffeeDinkDonk: 782, + toffeePat: 235, + toffeeTap: 815, + vp: 113, + wideVIBE: 972, + wideannyBop: 866, + widepeepoHappy: 969, + widepeepoMASTURBATION77769420GANGSHITNOMOREFORTNITE19DOLLERFORTNITECARD: 846, + xQchatting: 196, + xdd666: 700, + xddfdhjsd0f76ds5r26FDSFHD88hjdbsa67vr7xlLLhdsgfcxz632nkDFSGATVMVLN8CXFJJMVMMMM111111111111111111111: 866, + xdding: 735, + xqcCoomer: 397, + xqcGoofy: 360, + xqcRecord: 773, + xqcTwerk: 531, + yayAnny: 437, + yoshiJAM: 137, + zSpooky: 350, + zyzzBass: 680, +}; + +export { fakePrices }; diff --git a/pages/api/fakeRanking.ts b/pages/api/fakeRanking.ts deleted file mode 100644 index d7eb206..0000000 --- a/pages/api/fakeRanking.ts +++ /dev/null @@ -1,216 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -type Data = { - [key: string]: any; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const sortBy = req.query.s ? (req.query.s as string) : undefined; - const sortAsc = req.query.a ? (req.query.a as string) : undefined; - - let data = fakeData; - if (sortBy) { - if (sortBy === "netWorth") { - data = data.sort((a, b) => b.netWorth - a.netWorth); - } else if (sortBy === "dailyChange") { - data = data.sort((a, b) => b.dailyChange - a.dailyChange); - } else if (sortBy === "dailyChangePercent") { - data = data.sort((a, b) => b.dailyChangePercent - a.dailyChangePercent); - } else if (sortBy === "shares") { - data = data.sort((a, b) => b.shares - a.shares); - } else if (sortBy === "points") { - data = data.sort((a, b) => b.points - a.points); - } else if (sortBy === "name") { - data = data.sort((a, b) => a.name.localeCompare(b.name)); - } - if (sortAsc === "true") { - // slow but only needed for temporary fake data anyway - data = data.reverse(); - } - } - // fake loading time - await new Promise((resolve) => - setTimeout(resolve, 250 + Math.random() * 1000) - ); - res.status(200).json({ data }); -} - -interface fakeDataEntry { - id: number; - name: string; - netWorth: number; - points: number; - shares: number; - dailyChange: number; - dailyChangePercent: number; -} - -const fakeData: fakeDataEntry[] = [ - { - id: 4, - name: "3zachm", - netWorth: 10030, // stocks + points - points: 70, /// uninvested points - shares: 20, - dailyChange: -500, - dailyChangePercent: -0.0498504486540378863409770687936, - }, - { - id: 1, - name: "ModulatingForce", - netWorth: 142910, - points: 10020, - shares: 200, - dailyChange: 5420, - dailyChangePercent: 0.0379259673920649359736897347981, - }, - { - id: 2, - name: "notohh", - netWorth: 153495392, - points: 10020, - shares: 2432, - dailyChange: 0, - dailyChangePercent: 0, - }, - { - id: 3, - name: "SecondSockSan", - netWorth: 153495, - points: 15020, - shares: 20, - dailyChange: -10432, - dailyChangePercent: -0.06796312583471774324896576435715, - }, - { - id: 0, - name: "e__n__t__e", - netWorth: 429481824, - points: 1002022, - shares: 94214, - dailyChange: 329444422, - dailyChangePercent: 4.2932124926634939999741296760186, - }, - { - id: 5, - name: "luckytohavefoundyou14252", - netWorth: 8024, - points: 423, - shares: 4, - dailyChange: 9, - dailyChangePercent: 0.00112163509471585244267198404786, - }, - { - id: 6, - name: "ZeroxZerich", - netWorth: 842190, - points: 88542, - shares: 532, - dailyChange: -10219, - dailyChangePercent: -0.01213384153219582279533121979601, - }, - { - id: 7, - name: "joeeyo", - netWorth: 10000000, - points: 9999979, - shares: 1, - dailyChange: 1, - dailyChangePercent: 0.0000001, - }, - { - id: 8, - name: "dd_maru", - netWorth: 10328421, - points: 328421, - shares: 252, - dailyChange: 85192, - dailyChangePercent: 0.00824830823607984221402284047097, - }, - { - id: 9, - name: "Goldeneye128", - netWorth: 58292, - points: 6521, - shares: 63, - dailyChange: -1942, - dailyChangePercent: -0.03331503465312564331297605160228, - }, - { - id: 10, - name: "lilpastatv", - netWorth: 7328919, - points: 40, - shares: 93, - dailyChange: 921821, - dailyChangePercent: 0.12577857662228222197571019682439, - }, - { - id: 11, - name: "domiswitch", - netWorth: 43290, - points: 5002, - shares: 15, - dailyChange: 2429, - dailyChangePercent: 0.05610995610995610995610995610996, - }, - { - id: 12, - name: "minosura", - netWorth: 904328, - points: 32901, - shares: 83, - dailyChange: 94821, - dailyChangePercent: 0.10485244291894091524314186887943, - }, - { - id: 13, - name: "scienceteam_member", - netWorth: 34894, - points: 958, - shares: 5, - dailyChange: -7964, - dailyChangePercent: -0.22823408035765461110792686421734, - }, - { - id: 14, - name: "witchdev", - netWorth: 94382912, - points: 8532, - shares: 329, - dailyChange: -421, - dailyChangePercent: -0.0000044605531984433792422085896, - }, - { - id: 15, - name: "justone123879", - netWorth: 8889123, - points: 86333, - shares: 153, - dailyChange: 53289, - dailyChangePercent: 0.00599485461051669551653183334284, - }, - { - id: 16, - name: "marcelr_", - netWorth: 400329, - points: 39291, - shares: 52, - dailyChange: 1329, - dailyChangePercent: 0.00331976948959480827019776234047, - }, - { - id: 17, - name: "fossabot", - netWorth: 20005, - points: 0, - shares: 1, - dailyChange: -31042, - dailyChangePercent: -1.5517120719820044988752811797051, - }, -]; - -export type { fakeDataEntry }; diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts new file mode 100644 index 0000000..1e3c57d --- /dev/null +++ b/pages/api/fakeUsers.ts @@ -0,0 +1,629 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../misc/redis"; +import { getUserByName } from "../../misc/TwitchAPI"; +import { fakePrices } from "./fakePrices"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const username = req.query.u ? (req.query.u as string) : undefined; + const sortBy = req.query.s ? (req.query.s as string) : undefined; + const sortAsc = req.query.a ? (req.query.a as string) : undefined; + + const redis = createRedisInstance(); + + let data = fakeData; + // calculate all net worths + data = data.map((user) => { + return { + ...user, + net_worth: + user.points + + user.assets.reduce( + (a, b) => a + b.count * (fakePrices[b.name] ?? 0), + 0 + ), + }; + }); + // calculate ranking based on net worth + data = data.sort((a, b) => (b.net_worth ?? 0) - (a.net_worth ?? 0)); + data = data.map((user, i) => { + return { + ...user, + rank: i + 1, + // calculate total assets held (shares) + shares: user.assets.reduce((a, b) => a + b.count, 0), + // sort users badges by priority + badges: (user.badges ?? []).sort((a, b) => b.priority - a.priority ?? 0), + // sort users assets by total value + assets: user.assets.sort( + (a, b) => + (fakePrices[b.name] ?? 0) * b.count - + (fakePrices[a.name] ?? 0) * a.count + ), + }; + }); + + // if username is specified, only return that user + if (username) { + // if user does not exist, return error + data = data.filter((u) => u.name === username); + if (data.length === 0) { + res + .status(404) + .json({ error: { message: "User not found", code: 20000 } }); + return; + } + // get twitch data for user + const twitchData = await getUserByName(redis, username); + // if data is empty, user does not exist + if (twitchData.data.length === 0) { + // temp who cares + twitchData.data[0] = {}; + twitchData.data[0].profile_image_url = "/img/logo.webp"; + } + // add users profile picture url + data = data.map((u) => { + return { + ...u, + avatar_url: twitchData.data[0].profile_image_url ?? "", + }; + }); + res.status(200).json({ data: data[0] }); + return; + } + if (sortBy) { + if (sortBy === "daily_change") { + data = data.sort((a, b) => b.daily_change - a.daily_change); + } else if (sortBy === "daily_change_percent") { + data = data.sort( + (a, b) => b.daily_change_percent - a.daily_change_percent + ); + } else if (sortBy === "shares") { + data = data.sort((a, b) => (b.shares ?? 0) - (a.shares ?? 0)); + } else if (sortBy === "points") { + data = data.sort((a, b) => b.points - a.points); + } else if (sortBy === "name") { + data = data.sort((a, b) => a.name.localeCompare(b.name)); + } + if (sortAsc === "true") { + // slow but only needed for temporary fake data anyway + data = data.reverse(); + } + } + // fake loading time + await new Promise((resolve) => + setTimeout(resolve, 250 + Math.random() * 1000) + ); + res.status(200).json({ data: data }); +} + +interface asset { + name: string; + count: number; + provider: "7tv" | "bttv" | "ffz" | "ttv"; +} +interface fakeDataEntry { + id: number; + name: string; + points: number; + daily_change: number; + daily_change_percent: number; + assets: asset[]; + net_worth?: number; + shares?: number; + avatar_url?: string; + badges?: badge[]; +} + +interface badge { + name: string; + color: string; + priority: number; +} + +const adminBadge: badge = { + name: "Admin", + color: "#CC3333", + priority: 99999, +}; + +const CEOBadge: badge = { + name: "CEO", + color: "#F97316", + priority: 100000, +}; + +const webDevBadge: badge = { + name: "Web Dev", + color: "#a855f7", + priority: 50000, +}; + +const botDevBadge: badge = { + name: "Bot Dev", + color: "#48b2f1", + priority: 50001, +}; + +const fakeData: fakeDataEntry[] = [ + { + id: 4, + name: "3zachm", + points: 7420, // uninvested points + daily_change: -500, + daily_change_percent: -0.0498504486540378863409770687936, + assets: [ + { + name: "JIGACHAD", + count: 420, + provider: "7tv", + }, + { + name: "annykiss", + count: 8, + provider: "ttv", + }, + { + name: "HUH", + count: 1, + provider: "bttv", + }, + { + name: "widepeepoMASTURBATION77769420GANGSHITNOMOREFORTNITE19DOLLERFORTNITECARD", + count: 1, + provider: "ffz", + }, + { + name: "plink", + count: 1, + provider: "7tv", + }, + ], + badges: [adminBadge, webDevBadge], + }, + { + id: 1, + name: "ModulatingForce", + points: 10020, + daily_change: 5420, + daily_change_percent: 0.0379259673920649359736897347981, + assets: [ + { + name: "OhTheMiseryEverybodyWantsToBeMyEnemySpareTheSympathyEverybodyWantsToBeMyEnemy-y-y-y-y", + count: 39, + provider: "7tv", + }, + { + name: "Clueless", + count: 727, + provider: "7tv", + }, + ], + badges: [adminBadge, botDevBadge], + }, + { + id: 2, + name: "notohh", + points: 10020, + daily_change: 0, + daily_change_percent: 0, + assets: [ + { + name: "YourMom", + count: 81, + provider: "7tv", + }, + { + name: "CumTime", + count: 92, + provider: "7tv", + }, + { + name: "SNIFFA", + count: 1219, + provider: "7tv", + }, + ], + badges: [adminBadge, botDevBadge], + }, + { + id: 3, + name: "SecondSockSan", + points: 15020, + daily_change: -10432, + daily_change_percent: -0.06796312583471774324896576435715, + assets: [ + { + name: "AYAYAjam", + count: 46, + provider: "7tv", + }, + { + name: "ThisStream", + count: 210, + provider: "7tv", + }, + { + name: "BAND", + count: 91, + provider: "7tv", + }, + ], + badges: [CEOBadge, adminBadge], + }, + { + id: 0, + name: "mzntori", + points: 922022, + daily_change: 329444422, + daily_change_percent: 4.2932124926634939999741296760186, + assets: [ + { + name: "peepoSnow", + count: 72, + provider: "7tv", + }, + { + name: "golive", + count: 90, + provider: "7tv", + }, + { + name: "annyExcitedHug", + count: 26, + provider: "7tv", + }, + { + name: "AAAA", + count: 65, + provider: "7tv", + }, + ], + badges: [adminBadge, botDevBadge], + }, + { + id: 5, + name: "luckytohavefoundyou14252", + points: 423, + daily_change: 9, + daily_change_percent: 0.00112163509471585244267198404786, + assets: [ + { + name: "HACKERMANS", + count: 59, + provider: "7tv", + }, + { + name: "THIS", + count: 70, + provider: "7tv", + }, + { + name: "lebronJAM", + count: 66, + provider: "7tv", + }, + ], + }, + { + id: 6, + name: "ZeroxZerich", + points: 88542, + daily_change: -10219, + daily_change_percent: -0.01213384153219582279533121979601, + assets: [ + { + name: "WeebRun", + count: 10, + provider: "7tv", + }, + { + name: "BAND", + count: 49, + provider: "7tv", + }, + { + name: "SNIFFA", + count: 78, + provider: "7tv", + }, + ], + }, + { + id: 7, + name: "joeeyo", + points: 99979, + daily_change: 1, + daily_change_percent: 0.0000001, + assets: [ + { + name: "Siti", + count: 32, + provider: "7tv", + }, + { + name: "peepoTalk", + count: 73, + provider: "7tv", + }, + { + name: "peepoSnow", + count: 37, + provider: "7tv", + }, + { + name: "MadgeJuice", + count: 70, + provider: "7tv", + }, + { + name: "xdd666", + count: 53, + provider: "7tv", + }, + ], + }, + { + id: 8, + name: "dd_maru", + points: 208421, + daily_change: 85192, + daily_change_percent: 0.00824830823607984221402284047097, + assets: [ + { + name: "BocchiPossessed", + count: 56, + provider: "7tv", + }, + { + name: "toffeeConfused", + count: 64, + provider: "7tv", + }, + { + name: "ewLeague", + count: 64, + provider: "7tv", + }, + ], + }, + { + id: 9, + name: "Goldeneye128", + points: 6521, + daily_change: -1942, + daily_change_percent: -0.03331503465312564331297605160228, + assets: [ + { + name: "PagMan", + count: 52, + provider: "7tv", + }, + { + name: "CapybaraStare", + count: 47, + provider: "7tv", + }, + { + name: "GroupWankge", + count: 38, + provider: "7tv", + }, + { + name: "annyCucumber", + count: 90, + provider: "7tv", + }, + ], + }, + { + id: 10, + name: "lilpastatv", + points: 40, + daily_change: 921821, + daily_change_percent: 0.12577857662228222197571019682439, + assets: [ + { + name: "Wigglecat", + count: 205, + provider: "7tv", + }, + { + name: "guraWink", + count: 5, + provider: "7tv", + }, + { + name: "golive", + count: 46, + provider: "7tv", + }, + ], + }, + { + id: 11, + name: "domiswitch", + points: 5002, + daily_change: 2429, + daily_change_percent: 0.05610995610995610995610995610996, + assets: [ + { + name: "peepoFlute", + count: 81, + provider: "7tv", + }, + { + name: "WhoAsked", + count: 44, + provider: "7tv", + }, + { + name: "pL", + count: 24, + provider: "7tv", + }, + { + name: "peepoSnow", + count: 13, + provider: "7tv", + }, + ], + }, + { + id: 12, + name: "minosura", + points: 32901, + daily_change: 94821, + daily_change_percent: 0.10485244291894091524314186887943, + assets: [ + { + name: "Okayeg", + count: 100, + provider: "7tv", + }, + { + name: "burh", + count: 100, + provider: "7tv", + }, + { + name: "yoshiJAM", + count: 67, + provider: "7tv", + }, + { + name: "WhoAsked", + count: 59, + provider: "7tv", + }, + ], + }, + { + id: 13, + name: "scienceteam_member", + points: 958, + daily_change: -7964, + daily_change_percent: -0.22823408035765461110792686421734, + assets: [ + { + name: "LULE", + count: 43, + provider: "7tv", + }, + { + name: "Madgeclap", + count: 82, + provider: "7tv", + }, + { + name: "PeepoKittyHug", + count: 7, + provider: "7tv", + }, + ], + }, + { + id: 14, + name: "witchdev", + points: 8532, + daily_change: -421, + daily_change_percent: -0.0000044605531984433792422085896, + assets: [ + { + name: "SNIFFA", + count: 76, + provider: "7tv", + }, + { + name: "annyCD", + count: 62, + provider: "7tv", + }, + { + name: "anyatf", + count: 24, + provider: "7tv", + }, + ], + }, + { + id: 15, + name: "justone123879", + points: 86333, + daily_change: 53289, + daily_change_percent: 0.00599485461051669551653183334284, + assets: [ + { + name: "Homi", + count: 9, + provider: "7tv", + }, + { + name: "wideVIBE", + count: 61, + provider: "7tv", + }, + { + name: "Lagging", + count: 92, + provider: "7tv", + }, + ], + }, + { + id: 16, + name: "marcelr_", + points: 39291, + daily_change: 1329, + daily_change_percent: 0.00331976948959480827019776234047, + assets: [ + { + name: "peepoStuck", + count: 91, + provider: "7tv", + }, + { + name: "PokiShare", + count: 13, + provider: "7tv", + }, + { + name: "VeryBased", + count: 7, + provider: "7tv", + }, + ], + }, + { + id: 17, + name: "fossabot", + points: 0, + daily_change: -31042, + daily_change_percent: -1.5517120719820044988752811797051, + assets: [ + { + name: "catbaby", + count: 41, + provider: "7tv", + }, + { + name: "peepoCat", + count: 41, + provider: "7tv", + }, + { + name: "plink", + count: 32, + provider: "7tv", + }, + ], + }, +]; + +export type { fakeDataEntry }; diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 832718d..6328819 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -1,9 +1,10 @@ import { m, Variants } from "framer-motion"; import Head from "next/head"; +import Link from "next/link"; import { ReactElement, useEffect, useState } from "react"; import Loading from "../../components/common/Loading"; import DashLayout from "../../layouts/DashLayout"; -import { fakeDataEntry } from "../api/fakeRanking"; +import { fakeDataEntry } from "../api/fakeUsers"; function Ranking() { const [sortBy, setSortBy] = useState("netWorth"); @@ -14,7 +15,7 @@ function Ranking() { useEffect(() => { setDataLoaded(false); // fetch data from api on change to sort method - fetch(`/api/fakeRanking?s=${sortBy}&a=${sortAsc}`) + fetch(`/api/fakeUsers?s=${sortBy}&a=${sortAsc}`) .then((res) => res.json()) .then((data) => { setFakeData(data.data); @@ -78,13 +79,13 @@ function Ranking() { Ranking - toffee
-
+
{/* hidden if smaller than lg */} - Top Investors + top investors {/* TODO: responsive for extremely skinny displays (i.e. galaxy fold), or really for mobile entirely so info is not lost */} { // generate table rows - fakeData.map((entry: fakeDataEntry, index) => { - // if daily change is negative, make it red - let changeClass = " text-lime-500"; - if (entry.dailyChangePercent < 0) { - changeClass = " text-red-500"; + fakeData.map( + (entry: { [key: string]: any }, index: number) => { + // if daily change is negative, make it red + let changeClass = " text-lime-500"; + if (entry.daily_change_percent < 0) { + changeClass = " text-red-500"; + } + return ( + +

+ {index + 1} +

+ +

+ {entry.name} +

+ +

{entry.net_worth.toLocaleString("en-US")}

+

+ {entry.points.toLocaleString("en-US")} +

+

+ {entry.shares.toLocaleString("en-US")} +

+

+ {( + Math.round(entry.daily_change_percent * 1000) / + 10 + ).toFixed(1) + "%"} +

+
+ ); } - return ( - -

- {index + 1} -

-

- {entry.name} -

-

{entry.netWorth.toLocaleString("en-US")}

-

- {entry.points.toLocaleString("en-US")} -

-

- {entry.shares.toLocaleString("en-US")} -

-

- {( - Math.round(entry.dailyChangePercent * 1000) / 10 - ).toFixed(1) + "%"} -

-
- ); - }) + ) }
) diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx new file mode 100644 index 0000000..5b3f6ec --- /dev/null +++ b/pages/user/[username]/index.tsx @@ -0,0 +1,391 @@ +import { m } from "framer-motion"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { ReactElement, useEffect, useState } from "react"; +import DashLayout from "../../../layouts/DashLayout"; +import Image from "next/image"; +import Loading from "../../../components/common/Loading"; + +// TODO: Animations + +function UserPage() { + const [channelEmotes, setChannelEmotes] = useState<{ [key: string]: string }>( + {} + ); + const [userData, setUserData] = useState<{ [key: string]: any }>({}); + const [errorCode, setErrorCode] = useState(null); + const router = useRouter(); + const { username } = router.query; + const title = username ? `${username} - toffee` : "toffee"; + + useEffect(() => { + if (!router.isReady) return; + fetch("/api/7tv/emotes?c=61ad997effa9aba101bcfddf") + .then((res) => res.json()) + .then((data) => { + // if error, return + if (data.error) { + setErrorCode(data.error.code); + return; + } + // construct js object with emote names as keys and emote urls as values + let emotes: { [key: string]: string } = {}; + data.channel.user.emote_sets[0].emotes.forEach((emote: any) => { + let base_url = emote.data.host.url; + // get the largest emote size, append it to the base url + let largest = emote.data.host.files[emote.data.host.files.length - 1]; + emotes[emote.data.name] = `https:${base_url}/${largest.name}`; + }); + setChannelEmotes(emotes); + }); + fetch(`/api/fakeUsers?u=${username}`) + .then((res) => res.json()) + .then((data) => { + if (data.error) { + setErrorCode(data.error.code); + } + setUserData(data.data); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady]); + // if json is empty + if (Object.keys(channelEmotes).length === 0 && errorCode !== 10000) { + return ( +
+ +
+ ); + } + if (errorCode === 20000) { + return ( + +

User not found :c

+
+ ); + } + if (!userData || Object.keys(userData).length === 0) { + return ( +
+ +
+ ); + } + return ( + <> + + {title} + + +
+
+ {/* User "banner" */} +
+
+
+ User avatar +
+

+ {userData.name} +

+ {/* User's badges */} +
+ {userData.badges ? ( + userData.badges.map( + (badge: { + name: string; + color: string; + priority: number; + }) => { + return ( +
+ {badge.name} +
+ ); + } + ) + ) : ( + <> + )} +
+
+
+
+

+ + $ + + + {userData.net_worth.toLocaleString("en-US")} + +

+
+
+
+ {/* Main Container */} +
+ {/* User's Rank/Graph */} +
+
+
+

+ Global Rank +

+
+ # + + {userData.rank.toLocaleString("en-US")} + +
+
+
+ Rank chart +
+
+
+ {/* User's Assets */} +
+ {/* User's Assets Header */} +
+

+ Top Assets +

+
+ {/* User's Assets Body */} +
+ {errorCode === 20000 ? ( +

{`Could not load assets`}

+ ) : ( + userData.assets.map( + (asset: { + name: string; + count: number; + provider: string; + }) => ( +
+
+
+ { + // if error code is 10000, show placeholder image + errorCode === 10000 ? ( +

{`404 :(`}

+ ) : ( + {asset.name} + ) + } + {/* Fix asset count to bottom right of image */} +
+

+ x{asset.count} +

+
+
+
+
+ { + // show provider logo (7tv, bttv, ffz, twitch) + asset.provider === "7tv" ? ( +
+ +
+ ) : asset.provider === "bttv" ? ( +
+ +
+ ) : asset.provider === "ffz" ? ( +
+ +
+ ) : ( +
+ +
+ ) + } +

+ {asset.name} +

+
+
+ ) + ) + )} +
+
+
+ {/* Sidebar */} +
+
+ {/* User's Stats, left side is label, right side is value */} +

Points

+

{userData.points.toLocaleString("en-US")}

+

Shares

+

{userData.shares.toLocaleString("en-US")}

+

Trades

+

{(userData.trades ?? 0).toLocaleString("en-US")}

+

Peak rank

+

{(userData.peak_rank ?? 0).toLocaleString("en-US")}

+

Joined

+

+ {new Date(userData.joined ?? 0).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + })} +

+
+ {/* User's Favorite Emote */} +
+
+

+ Favorite Emote +

+
+
+

+ This user has not yet set a favorite emote. +

+
+
+
+
+
+ + ); +} + +const SevenTVLogo = () => { + return ( + + + + + + + + ); +}; + +const FFZLogo = () => { + return ( + + + + ); +}; + +const BTTVLogo = () => { + return ( + + + + + + ); +}; + +const TwitchLogo = () => { + return ( + + + + + + + + + + + + + ); +}; + +UserPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default UserPage; diff --git a/public/img/well_drawn_rank_chart.webp b/public/img/well_drawn_rank_chart.webp new file mode 100644 index 0000000000000000000000000000000000000000..39d254f0e2d777d39dca580d3e56393bbfead43f GIT binary patch literal 14714 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 literal 0 HcmV?d00001 diff --git a/tailwind.config.js b/tailwind.config.js index 325ade7..0685455 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,6 +13,11 @@ module.exports = { robotoMono: ["Roboto Mono", "monospace"], minecraft: ["Minecraft", "Roboto", "sans-serif"], }, + colors: { + "7tv": "#4fc2bc", + bttv: "#d50014", + ttv: "#9146FF", + }, }, }, plugins: [], From 67eb895988178fd9435e5878720c925aa5e8e8f0 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Fri, 20 Jan 2023 02:47:51 -0800 Subject: [PATCH 07/13] type fix --- misc/7TVAPI.tsx | 10 +++++----- misc/TwitchAPI.tsx | 17 +++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/misc/7TVAPI.tsx b/misc/7TVAPI.tsx index 5ffebf3..b7bfad1 100644 --- a/misc/7TVAPI.tsx +++ b/misc/7TVAPI.tsx @@ -1,7 +1,7 @@ -import type RedisInstance from "ioredis"; +import Redis from "ioredis"; async function applyCache( - redis: RedisInstance, + redis: Redis, key: string, query: string, gql: boolean, @@ -19,7 +19,7 @@ async function applyCache( } async function fetchEndpoint( - redis: RedisInstance, + redis: Redis, query: string, gql: boolean = false ) { @@ -53,7 +53,7 @@ async function fetchGQL(query: string) { return json; } -async function getGlobalEmotes(redis: RedisInstance) { +async function getGlobalEmotes(redis: Redis) { const gqlQuery = `query { namedEmoteSet(name: GLOBAL) { emote_count @@ -84,7 +84,7 @@ async function getGlobalEmotes(redis: RedisInstance) { return await applyCache(redis, "7TV.GLOBAL_EMOTES", gqlQuery, true, 3600); } -async function getChannelEmotes(redis: RedisInstance, channelID: string) { +async function getChannelEmotes(redis: Redis, channelID: string) { const gqlQuery = `query { user(id: "${channelID}") { emote_sets { diff --git a/misc/TwitchAPI.tsx b/misc/TwitchAPI.tsx index b72a6f1..c0573e5 100644 --- a/misc/TwitchAPI.tsx +++ b/misc/TwitchAPI.tsx @@ -1,7 +1,8 @@ -import type RedisInstance from "ioredis"; +// ioredis types +import Redis from "ioredis"; async function applyCache( - redis: RedisInstance, + redis: Redis, key: string, query: string, cacheTime: number @@ -17,7 +18,7 @@ async function applyCache( } } -async function authTwitch(redis: RedisInstance) { +async function authTwitch(redis: Redis) { let auth = await redis.get("TWITCH.AUTH"); if (auth) { return auth; @@ -35,7 +36,7 @@ async function authTwitch(redis: RedisInstance) { } } -async function fetchEndpoint(redis: RedisInstance, query: string) { +async function fetchEndpoint(redis: Redis, query: string) { if (await redis.get("TWITCH.RATE_LIMIT")) { await new Promise((resolve) => setTimeout(resolve, 1000)); } else { @@ -55,7 +56,7 @@ async function fetchEndpoint(redis: RedisInstance, query: string) { return json; } -async function getUserByName(redis: RedisInstance, username: string) { +async function getUserByName(redis: Redis, username: string) { return await applyCache( redis, "TWITCH.USER_" + username, @@ -64,7 +65,7 @@ async function getUserByName(redis: RedisInstance, username: string) { ); } -async function getUserByID(redis: RedisInstance, userID: string) { +async function getUserByID(redis: Redis, userID: string) { return await applyCache( redis, "TWITCH.USER_" + userID, @@ -73,7 +74,7 @@ async function getUserByID(redis: RedisInstance, userID: string) { ); } -async function getGlobalEmotes(redis: RedisInstance) { +async function getGlobalEmotes(redis: Redis) { return await applyCache( redis, "TWITCH.GLOBAL_EMOTES", @@ -82,7 +83,7 @@ async function getGlobalEmotes(redis: RedisInstance) { ); } -async function getChannelEmotes(redis: RedisInstance, channelID: string) { +async function getChannelEmotes(redis: Redis, channelID: string) { return await applyCache( redis, "TWITCH.CHANNEL_EMOTES_" + channelID, From be3b85576003f23ca43f856f8742a3f545f174e4 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachn4@gmail.com> Date: Fri, 20 Jan 2023 14:23:25 -0800 Subject: [PATCH 08/13] revert type fix --- misc/7TVAPI.tsx | 10 +++++----- misc/TwitchAPI.tsx | 17 ++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/misc/7TVAPI.tsx b/misc/7TVAPI.tsx index b7bfad1..51895de 100644 --- a/misc/7TVAPI.tsx +++ b/misc/7TVAPI.tsx @@ -1,7 +1,7 @@ -import Redis from "ioredis"; +import RedisInstance from "ioredis"; async function applyCache( - redis: Redis, + redis: RedisInstance, key: string, query: string, gql: boolean, @@ -19,7 +19,7 @@ async function applyCache( } async function fetchEndpoint( - redis: Redis, + redis: RedisInstance, query: string, gql: boolean = false ) { @@ -53,7 +53,7 @@ async function fetchGQL(query: string) { return json; } -async function getGlobalEmotes(redis: Redis) { +async function getGlobalEmotes(redis: RedisInstance) { const gqlQuery = `query { namedEmoteSet(name: GLOBAL) { emote_count @@ -84,7 +84,7 @@ async function getGlobalEmotes(redis: Redis) { return await applyCache(redis, "7TV.GLOBAL_EMOTES", gqlQuery, true, 3600); } -async function getChannelEmotes(redis: Redis, channelID: string) { +async function getChannelEmotes(redis: RedisInstance, channelID: string) { const gqlQuery = `query { user(id: "${channelID}") { emote_sets { diff --git a/misc/TwitchAPI.tsx b/misc/TwitchAPI.tsx index c0573e5..ab16d86 100644 --- a/misc/TwitchAPI.tsx +++ b/misc/TwitchAPI.tsx @@ -1,8 +1,7 @@ -// ioredis types -import Redis from "ioredis"; +import RedisInstance from "ioredis"; async function applyCache( - redis: Redis, + redis: RedisInstance, key: string, query: string, cacheTime: number @@ -18,7 +17,7 @@ async function applyCache( } } -async function authTwitch(redis: Redis) { +async function authTwitch(redis: RedisInstance) { let auth = await redis.get("TWITCH.AUTH"); if (auth) { return auth; @@ -36,7 +35,7 @@ async function authTwitch(redis: Redis) { } } -async function fetchEndpoint(redis: Redis, query: string) { +async function fetchEndpoint(redis: RedisInstance, query: string) { if (await redis.get("TWITCH.RATE_LIMIT")) { await new Promise((resolve) => setTimeout(resolve, 1000)); } else { @@ -56,7 +55,7 @@ async function fetchEndpoint(redis: Redis, query: string) { return json; } -async function getUserByName(redis: Redis, username: string) { +async function getUserByName(redis: RedisInstance, username: string) { return await applyCache( redis, "TWITCH.USER_" + username, @@ -65,7 +64,7 @@ async function getUserByName(redis: Redis, username: string) { ); } -async function getUserByID(redis: Redis, userID: string) { +async function getUserByID(redis: RedisInstance, userID: string) { return await applyCache( redis, "TWITCH.USER_" + userID, @@ -74,7 +73,7 @@ async function getUserByID(redis: Redis, userID: string) { ); } -async function getGlobalEmotes(redis: Redis) { +async function getGlobalEmotes(redis: RedisInstance) { return await applyCache( redis, "TWITCH.GLOBAL_EMOTES", @@ -83,7 +82,7 @@ async function getGlobalEmotes(redis: Redis) { ); } -async function getChannelEmotes(redis: Redis, channelID: string) { +async function getChannelEmotes(redis: RedisInstance, channelID: string) { return await applyCache( redis, "TWITCH.CHANNEL_EMOTES_" + channelID, From 53cc78a43a0bed06150ba2b6670250cd413cd321 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachn4@gmail.com> Date: Fri, 20 Jan 2023 14:38:03 -0800 Subject: [PATCH 09/13] overflow fix on grid --- pages/ranking/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 6328819..5c6bb14 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -179,7 +179,10 @@ function Ranking() {

{index + 1}

- +

{entry.name}

From 0a9287ab4f5bc70d2dcbea05770b73d973122243 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachn4@gmail.com> Date: Fri, 20 Jan 2023 15:20:40 -0800 Subject: [PATCH 10/13] api error handling and avatar fix --- pages/api/fakeUsers.ts | 12 ++++++- pages/user/[username]/index.tsx | 64 ++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index 1e3c57d..adddc18 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -60,7 +60,17 @@ export default async function handler( return; } // get twitch data for user - const twitchData = await getUserByName(redis, username); + let twitchData: { data: { [key: string]: any }[] }; + try { + twitchData = await getUserByName(redis, username); + } catch (e) { + res + .status(500) + .json({ + error: { message: "Twitch or internal API is down", code: 10100 }, + }); + return; + } // if data is empty, user does not exist if (twitchData.data.length === 0) { // temp who cares diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 5b3f6ec..9a46fb7 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -48,15 +48,12 @@ function UserPage() { }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); - // if json is empty - if (Object.keys(channelEmotes).length === 0 && errorCode !== 10000) { - return ( -
- -
- ); - } - if (errorCode === 20000) { + + if (errorCode !== null) { + // 20000 = user not found + // 10000 = 7tv api error + // 10100 = Twitch api error + const errorMsg = errorCode === 20000 ? "User not found" : "API error"; return ( -

User not found :c

+

{errorMsg}

); } - if (!userData || Object.keys(userData).length === 0) { + + // if json is empty + if ( + Object.keys(channelEmotes).length === 0 || + !userData || + Object.keys(userData).length === 0 + ) { return (
); } + return ( <> @@ -90,25 +94,27 @@ function UserPage() {
- User avatar + User avatar -
+ : "grey", + // "glow" effect + boxShadow: `0px 0px 20px 1px ${ + userData.badges[0] + ? userData.badges[0].color + : "transparent" + }`, + }} + /> +
+

{userData.name}

From 7d3b0d2554f438b099e6e1d472b41f7b589170b9 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Fri, 20 Jan 2023 18:24:31 -0800 Subject: [PATCH 11/13] responsive design --- layouts/DashLayout.tsx | 1 + misc/7TVAPI.tsx | 2 +- pages/user/[username]/index.tsx | 130 +++++++++++++++++--------------- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/layouts/DashLayout.tsx b/layouts/DashLayout.tsx index addd205..612004c 100644 --- a/layouts/DashLayout.tsx +++ b/layouts/DashLayout.tsx @@ -25,6 +25,7 @@ function DashLayout(props: DashLayoutProps) { variants={containerVariants} > + Dashboard - toffee diff --git a/misc/7TVAPI.tsx b/misc/7TVAPI.tsx index 51895de..5ffebf3 100644 --- a/misc/7TVAPI.tsx +++ b/misc/7TVAPI.tsx @@ -1,4 +1,4 @@ -import RedisInstance from "ioredis"; +import type RedisInstance from "ioredis"; async function applyCache( redis: RedisInstance, diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index 9a46fb7..cdd3e56 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -89,7 +89,7 @@ function UserPage() { />
-
+
{/* User "banner" */}
@@ -144,7 +144,7 @@ function UserPage() {
-
+

$ @@ -157,12 +157,12 @@ function UserPage() {

{/* Main Container */} -
+
{/* User's Rank/Graph */}
-
-

+
+

Global Rank

@@ -172,7 +172,7 @@ function UserPage() {
-
+
Rank chart
+
+

+ + $ + + + {userData.net_worth.toLocaleString("en-US")} + +

+

{/* User's Assets */} @@ -191,7 +201,7 @@ function UserPage() {
{/* User's Assets Body */} -
+
{errorCode === 20000 ? (

{`Could not load assets`}

) : ( @@ -202,60 +212,62 @@ function UserPage() { provider: string; }) => (
-
-
- { - // if error code is 10000, show placeholder image - errorCode === 10000 ? ( -

{`404 :(`}

- ) : ( - {asset.name} - ) - } - {/* Fix asset count to bottom right of image */} -
-

- x{asset.count} -

+
+
+
+ { + // if error code is 10000, show placeholder image + errorCode === 10000 ? ( +

{`404 :(`}

+ ) : ( + {asset.name} + ) + } + {/* Fix asset count to bottom right of image */} +
+

+ x{asset.count} +

+
-
-
- { - // show provider logo (7tv, bttv, ffz, twitch) - asset.provider === "7tv" ? ( -
- -
- ) : asset.provider === "bttv" ? ( -
- -
- ) : asset.provider === "ffz" ? ( -
- -
- ) : ( -
- -
- ) - } -

- {asset.name} -

+
+ { + // show provider logo (7tv, bttv, ffz, twitch) + asset.provider === "7tv" ? ( +
+ +
+ ) : asset.provider === "bttv" ? ( +
+ +
+ ) : asset.provider === "ffz" ? ( +
+ +
+ ) : ( +
+ +
+ ) + } +

+ {asset.name} +

+
) @@ -265,8 +277,8 @@ function UserPage() {
{/* Sidebar */} -
-
+
+
{/* User's Stats, left side is label, right side is value */}

Points

{userData.points.toLocaleString("en-US")}

From 37636cd583117377b8dcad47351ee04eb0a92165 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Fri, 20 Jan 2023 20:06:18 -0800 Subject: [PATCH 12/13] extra random user --- pages/api/fakeUsers.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index adddc18..5cc6839 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -64,11 +64,9 @@ export default async function handler( try { twitchData = await getUserByName(redis, username); } catch (e) { - res - .status(500) - .json({ - error: { message: "Twitch or internal API is down", code: 10100 }, - }); + res.status(500).json({ + error: { message: "Twitch or internal API is down", code: 10100 }, + }); return; } // if data is empty, user does not exist @@ -634,6 +632,30 @@ const fakeData: fakeDataEntry[] = [ }, ], }, + { + id: 18, + name: "Headdesking1", + points: 429, + daily_change: 0, + daily_change_percent: 0, + assets: [ + { + name: "anyaPls", + count: 92, + provider: "7tv", + }, + { + name: "toffeeDinkDonk", + count: 6, + provider: "7tv", + }, + { + name: "SoCute", + count: 99, + provider: "7tv", + }, + ], + }, ]; export type { fakeDataEntry }; From 18137632f64a6375b0787f9274f19ace32d8a339 Mon Sep 17 00:00:00 2001 From: 3zachm <3zachm2@gmail.com> Date: Sat, 21 Jan 2023 02:36:29 -0800 Subject: [PATCH 13/13] ffz, bttv, twitch APIs init --- misc/BTTVAPI.tsx | 58 ++++ misc/FFZAPI.tsx | 58 ++++ next.config.js | 8 +- pages/api/bttv/emotes.ts | 30 +++ pages/api/fakePrices.ts | 464 ++++++++++++++++++++++++++++++++ pages/api/fakeUsers.ts | 217 ++++++++++++++- pages/api/ffz/emotes.ts | 30 +++ pages/api/twitch/emotes.ts | 30 +++ pages/user/[username]/index.tsx | 101 ++++++- 9 files changed, 972 insertions(+), 24 deletions(-) create mode 100644 misc/BTTVAPI.tsx create mode 100644 misc/FFZAPI.tsx create mode 100644 pages/api/bttv/emotes.ts create mode 100644 pages/api/ffz/emotes.ts create mode 100644 pages/api/twitch/emotes.ts diff --git a/misc/BTTVAPI.tsx b/misc/BTTVAPI.tsx new file mode 100644 index 0000000..675c58c --- /dev/null +++ b/misc/BTTVAPI.tsx @@ -0,0 +1,58 @@ +import type RedisInstance from "ioredis"; + +async function applyCache( + redis: RedisInstance, + key: string, + query: string, + + cacheTime: number +) { + if (await redis.get(key)) { + return JSON.parse((await redis.get(key)) as string); + } else { + const response = await fetchEndpoint(redis, query); + if (response != null) { + await redis.set(key, JSON.stringify(response), "EX", cacheTime); + } + return response; + } +} + +async function fetchEndpoint(redis: RedisInstance, query: string) { + if (await redis.get("BTTV.RATE_LIMIT")) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + await redis.set("BTTV.RATE_LIMIT", "1", "EX", 1); + } + + const requestHeaders = new Headers(); + requestHeaders.append("Content-Type", "application/json"); + requestHeaders.append("User-Agent", "toffee-web/indev"); + + const response = await fetch(query, { + headers: requestHeaders, + }); + + const json = await response.json(); + return json; +} + +async function getGlobalEmotes(redis: RedisInstance) { + return await applyCache( + redis, + "BTTV.GLOBAL_EMOTES", + "https://api.betterttv.net/3/cached/emotes/global", + 3600 + ); +} + +async function getUserByID(redis: RedisInstance, channelID: string) { + return await applyCache( + redis, + `BTTV.CHANNEL_EMOTES.${channelID}`, + `https://api.betterttv.net/3/cached/users/twitch/${channelID}`, + 3600 + ); +} + +export { getGlobalEmotes, getUserByID }; diff --git a/misc/FFZAPI.tsx b/misc/FFZAPI.tsx new file mode 100644 index 0000000..06cb411 --- /dev/null +++ b/misc/FFZAPI.tsx @@ -0,0 +1,58 @@ +import type RedisInstance from "ioredis"; + +async function applyCache( + redis: RedisInstance, + key: string, + query: string, + + cacheTime: number +) { + if (await redis.get(key)) { + return JSON.parse((await redis.get(key)) as string); + } else { + const response = await fetchEndpoint(redis, query); + if (response != null) { + await redis.set(key, JSON.stringify(response), "EX", cacheTime); + } + return response; + } +} + +async function fetchEndpoint(redis: RedisInstance, query: string) { + if (await redis.get("FFZ.RATE_LIMIT")) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + await redis.set("FFZ.RATE_LIMIT", "1", "EX", 1); + } + + const requestHeaders = new Headers(); + requestHeaders.append("Content-Type", "application/json"); + requestHeaders.append("User-Agent", "toffee-web/indev"); + + const response = await fetch(query, { + headers: requestHeaders, + }); + + const json = await response.json(); + return json; +} + +async function getGlobalEmotes(redis: RedisInstance) { + return await applyCache( + redis, + "FFZ.GLOBAL_EMOTES", + "https://api.frankerfacez.com/v1/set/global", + 3600 + ); +} + +async function getEmoteSet(redis: RedisInstance, setID: string) { + return await applyCache( + redis, + `FFZ.EMOTE_SET.${setID}`, + `https://api.frankerfacez.com/v1/set/${setID}`, + 3600 + ); +} + +export { getGlobalEmotes, getEmoteSet }; diff --git a/next.config.js b/next.config.js index bd38de3..6f65b5a 100644 --- a/next.config.js +++ b/next.config.js @@ -3,7 +3,13 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, images: { - domains: ["cdn.discordapp.com", "static-cdn.jtvnw.net", "cdn.7tv.app"], + domains: [ + "cdn.discordapp.com", + "static-cdn.jtvnw.net", + "cdn.7tv.app", + "cdn.betterttv.net", + "cdn.frankerfacez.com", + ], }, }; diff --git a/pages/api/bttv/emotes.ts b/pages/api/bttv/emotes.ts new file mode 100644 index 0000000..fbdf506 --- /dev/null +++ b/pages/api/bttv/emotes.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../../misc/redis"; +import { getUserByID, getGlobalEmotes } from "../../../misc/BTTVAPI"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const redis = createRedisInstance(); + + try { + const channel = req.query.c + ? (await getUserByID(redis, req.query.c as string)).channelEmotes + : undefined; + const global = await getGlobalEmotes(redis); + redis.quit(); + res.status(200).json({ channel, global }); + } catch (e) { + console.log(e); + res + .status(500) + .json({ + error: { message: "BTTV or internal API is down", code: 10200 }, + }); + } +} diff --git a/pages/api/fakePrices.ts b/pages/api/fakePrices.ts index b801d38..85fe5e9 100644 --- a/pages/api/fakePrices.ts +++ b/pages/api/fakePrices.ts @@ -335,6 +335,470 @@ const fakePrices: { [key: string]: number } = { yoshiJAM: 137, zSpooky: 350, zyzzBass: 680, + // bttv + ":tf:": 325, + CiGrip: 697, + DatSauce: 903, + ForeverAlone: 663, + GabeN: 497, + HailHelix: 447, + ShoopDaWhoop: 502, + "M&Mjc": 196, + bttvNice: 558, + TwaT: 176, + WatChuSay: 947, + tehPoleCat: 611, + TaxiBro: 527, + BroBalt: 384, + CandianRage: 431, + "D:": 192, + VisLaud: 966, + KaRappa: 247, + FishMoley: 276, + Hhhehehe: 945, + KKona: 596, + PoleDoge: 485, + sosGame: 473, + CruW: 591, + RarePepe: 866, + haHAA: 979, + FeelsBirthdayMan: 536, + RonSmug: 776, + KappaCool: 777, + FeelsBadMan: 483, + bUrself: 934, + ConcernDoge: 520, + FeelsGoodMan: 202, + FireSpeed: 82, + NaM: 412, + SourPls: 231, + FeelsSnowMan: 639, + FeelsSnowyMan: 929, + LuL: 603, + SoSnowy: 647, + SaltyCorn: 486, + monkaS: 189, + VapeNation: 570, + ariW: 926, + notsquishY: 438, + FeelsAmazingMan: 92, + DuckerZ: 417, + IceCold: 21, + SqShy: 84, + Wowee: 533, + WubTF: 494, + cvR: 913, + cvL: 337, + cvHazmat: 599, + cvMask: 423, + DogChamp: 947, + annytfBanana: 730, + annySaur: 236, + annytfBlink: 108, + AnnySilly: 469, + annyBlankies: 747, + annyHop: 861, + annyHopper: 941, + annyTeaTime: 398, + annyD: 530, + annyDVibe: 907, + annyDHyper: 451, + annyDFast: 117, + KissaVei: 158, + Annie: 597, + annyPls: 91, + // ffz + monkaW: 969, + "5Head": 750, + Prayge: 434, + PauseChamp: 903, + Pepega: 817, + TWINGO: 256, + PepeHands: 190, + monkaTOS: 146, + monkaHmm: 549, + Dolan: 536, + SmileW: 166, + ABABABABA: 95, + peepoBlanket: 798, + pikachuS: 715, + AYAYAHyper: 164, + YEP: 371, + widepeepoBlanket: 100, + HandsUp: 684, + peepoSad: 167, + HyperKorone: 456, + AYAYA: 503, + forsenCD: 344, + Hahaa: 291, + LULW: 463, + WICKED: 133, + EZY: 494, + OkayChamp: 950, + PepegaPig: 217, + POGGIES: 487, + peepoWTF: 734, + ConfusedCat: 508, + PainPeko: 433, + KKrikey: 235, + COPIUM: 582, + Madge: 595, + Catge: 811, + stopbeingMean: 522, + NOPE: 262, + OMEGALUL: 648, + AYAYAY: 725, + PogO: 548, + Sadge: 41, + PepegaPhone: 800, + Widega: 543, + ZrehplaR: 23, + YooHoo: 617, + ManChicken: 588, + BeanieHipster: 480, + CatBag: 253, + ZreknarF: 143, + LilZ: 952, + ZliL: 662, + LaterSooner: 375, + BORT: 738, + OBOY: 942, + OiMinna: 354, + AndKnuckles: 828, + // twitch + annytfRaid: 493, + annytfNote: 893, + annytfPout: 971, + annytfAyaya: 180, + annytfPika: 328, + annytfPrime: 830, + annytfLewd: 314, + annytfRave: 615, + annytfLurk: 898, + annytfPog: 958, + annytfD: 759, + annytfCry: 399, + annytfHeart: 472, + annytfSad: 390, + annytfMelt: 157, + annytfWICKED: 94, + annytfCheer: 780, + annytfREEE: 272, + annytfLUL: 122, + annytfScuffed: 318, + annytfAngy: 677, + annytfHug: 826, + annytfCool: 121, + annytfPain: 16, + annytfBonk: 949, + annytfKnuckles: 182, + annytfSigh: 220, + annytfWoah: 582, + annytfBite: 580, + annytfSilly: 837, + annytfWow: 381, + annytfPray: 438, + annytfPats: 410, + annytfGasm: 822, + annytfSit: 39, + annytfFlower: 137, + annytfLeave: 819, + annytfGamba: 847, + annytfLuv: 483, + annytfHarucchiHug: 142, + annytfAnnE: 324, + annytfDinkDonk: 380, + annytfAhriluv: 232, + annytfW: 101, + annytfWoke: 145, + annytfBedge: 409, + annytfBusiness: 142, + annytfPeek: 98, + annytfDonowall: 581, + NewRecord: 836, + Awwdible: 794, + Lechonk: 567, + Getcamped: 378, + SUBprise: 580, + FallHalp: 95, + FallCry: 450, + FallWinning: 261, + MechaRobot: 984, + ImTyping: 993, + Shush: 506, + MyAvatar: 41, + PizzaTime: 330, + LaundryBasket: 852, + ModLove: 683, + PotFriend: 605, + Jebasted: 72, + PogBones: 761, + PoroSad: 357, + KEKHeim: 290, + CaitlynS: 874, + HarleyWink: 244, + WhySoSerious: 705, + DarkKnight: 101, + FamilyMan: 278, + RyuChamp: 814, + HungryPaimon: 920, + TransgenderPride: 409, + PansexualPride: 707, + NonbinaryPride: 866, + LesbianPride: 763, + IntersexPride: 136, + GenderFluidPride: 73, + GayPride: 471, + BisexualPride: 163, + AsexualPride: 533, + PogChamp: 898, + GlitchNRG: 877, + GlitchLit: 45, + StinkyGlitch: 697, + GlitchCat: 208, + FootGoal: 542, + FootYellow: 297, + FootBall: 679, + BlackLivesMatter: 637, + ExtraLife: 394, + VirtualHug: 452, + "R-)": 779, + "R)": 242, + ";-p": 911, + ";p": 168, + ";-P": 584, + ";P": 571, + ":-p": 758, + ":p": 709, + ":-P": 137, + ":P": 602, + ";-)": 349, + ";)": 745, + ":-\\": 30, + ":\\": 725, + ":-/": 403, + ":/": 187, + "<3": 51, + ":-o": 622, + ":o": 384, + ":-O": 994, + ":O": 629, + "8-)": 881, + "B-)": 390, + "B)": 489, + "o.o": 570, + o_o: 738, + "o.O": 287, + o_O: 168, + "O.O": 452, + O_O: 33, + "O.o": 187, + O_o: 513, + ":-Z": 729, + ":Z": 762, + ":-z": 658, + ":z": 526, + ":-|": 978, + ":|": 224, + ">(": 141, + ":-D": 699, + ":D": 926, + ":-(": 864, + ":-)": 141, + BOP: 826, + SingsNote: 46, + SingsMic: 347, + TwitchSings: 419, + SoonerLater: 187, + HolidayTree: 822, + HolidaySanta: 340, + HolidayPresent: 830, + HolidayLog: 251, + HolidayCookie: 105, + GunRun: 899, + PixelBob: 829, + FBPenalty: 123, + FBChallenge: 645, + FBCatch: 769, + FBBlock: 284, + FBSpiral: 633, + FBPass: 365, + FBRun: 688, + MaxLOL: 17, + TwitchRPG: 258, + PinkMercy: 369, + MercyWing2: 2, + MercyWing1: 546, + PartyHat: 581, + EarthDay: 649, + TombRaid: 904, + PopCorn: 485, + FBtouchdown: 621, + TPFufun: 321, + TwitchVotes: 858, + DarkMode: 90, + HSWP: 729, + HSCheers: 802, + PowerUpL: 88, + PowerUpR: 616, + LUL: 958, + EntropyWins: 639, + TPcrunchyroll: 286, + TwitchUnity: 349, + Squid4: 548, + Squid3: 113, + Squid2: 768, + Squid1: 649, + CrreamAwk: 186, + CarlSmile: 822, + TwitchLit: 125, + TehePelo: 124, + TearGlove: 354, + SabaPing: 94, + PunOko: 145, + KonCha: 656, + Kappu: 597, + InuyoFace: 434, + BigPhish: 169, + BegWan: 621, + ThankEgg: 391, + MorphinTime: 106, + TheIlluminati: 531, + TBAngel: 925, + MVGame: 873, + NinjaGrumpy: 345, + PartyTime: 773, + RlyTho: 830, + UWot: 265, + YouDontSay: 744, + KAPOW: 757, + ItsBoshyTime: 605, + CoolStoryBob: 193, + TriHard: 121, + SuperVinlin: 500, + FreakinStinkin: 860, + Poooound: 411, + CurseLit: 318, + BatChest: 642, + BrainSlug: 48, + PrimeMe: 619, + StrawBeary: 813, + RaccAttack: 172, + UncleNox: 583, + WTRuck: 118, + TooSpicy: 761, + Jebaited: 363, + DogFace: 911, + BlargNaut: 148, + TakeNRG: 27, + GivePLZ: 581, + imGlitch: 514, + pastaThat: 48, + copyThis: 426, + UnSane: 97, + DatSheffy: 289, + TheTarFu: 818, + PicoMause: 570, + TinyFace: 31, + DxCat: 538, + RuleFive: 903, + VoteNay: 113, + VoteYea: 223, + PJSugar: 11, + DoritosChip: 187, + OpieOP: 977, + FutureMan: 893, + ChefFrank: 481, + StinkyCheese: 419, + NomNom: 162, + SmoocherZ: 863, + cmonBruh: 93, + KappaWealth: 776, + MikeHogu: 497, + VoHiYo: 646, + KomodoHype: 295, + SeriousSloth: 379, + OSFrog: 807, + OhMyDog: 124, + KappaClaus: 209, + KappaRoss: 298, + MingLee: 338, + SeemsGood: 89, + twitchRaid: 258, + bleedPurple: 949, + duDudu: 442, + riPepperonis: 192, + NotLikeThis: 838, + DendiFace: 534, + CoolCat: 995, + KappaPride: 915, + ShadyLulu: 372, + ArgieB8: 267, + CorgiDerp: 511, + PraiseIt: 557, + TTours: 122, + mcaT: 154, + NotATK: 388, + HeyGuys: 453, + Mau5: 421, + PRChase: 443, + WutFace: 20, + BuddhaBar: 622, + PermaSmug: 769, + panicBasket: 285, + BabyRage: 315, + HassaanChop: 246, + TheThing: 890, + EleGiggle: 284, + RitzMitz: 671, + YouWHY: 796, + PipeHype: 343, + BrokeBack: 440, + ANELE: 156, + PanicVis: 865, + GrammarKing: 77, + PeoplesChamp: 634, + SoBayed: 700, + BigBrother: 657, + Keepo: 800, + Kippa: 835, + RalpherZ: 322, + TF2John: 862, + ThunBeast: 408, + WholeWheat: 193, + DAESuppy: 787, + FailFish: 395, + HotPokket: 399, + ResidentSleeper: 460, + FUNgineer: 747, + PMSTwin: 830, + ShazBotstix: 315, + BibleThump: 278, + AsianGlow: 461, + DBstyle: 968, + BloodTrail: 687, + OneHand: 801, + FrankerZ: 893, + SMOrc: 727, + ArsonNoSexy: 99, + PunchTrees: 762, + SSSsss: 800, + Kreygasm: 413, + KevinTurtle: 111, + PJSalt: 115, + SwiftRage: 251, + DansGame: 46, + GingerPower: 762, + BCWarrior: 409, + MrDestructoid: 811, + JonCarnage: 359, + Kappa: 40, + RedCoat: 789, + TheRinger: 669, + StoneLightning: 867, + OptimizePrime: 654, + JKanStyle: 655, + ":)": 594, }; export { fakePrices }; diff --git a/pages/api/fakeUsers.ts b/pages/api/fakeUsers.ts index 5cc6839..052e9dc 100644 --- a/pages/api/fakeUsers.ts +++ b/pages/api/fakeUsers.ts @@ -175,15 +175,25 @@ const fakeData: fakeDataEntry[] = [ { name: "annykiss", count: 8, - provider: "ttv", + provider: "7tv", }, { name: "HUH", count: 1, + provider: "7tv", + }, + { + name: "annytfSigh", + count: 1, + provider: "ttv", + }, + { + name: "GabeN", + count: 3, provider: "bttv", }, { - name: "widepeepoMASTURBATION77769420GANGSHITNOMOREFORTNITE19DOLLERFORTNITECARD", + name: "widepeepoBlanket", count: 1, provider: "ffz", }, @@ -212,6 +222,21 @@ const fakeData: fakeDataEntry[] = [ count: 727, provider: "7tv", }, + { + name: "AnnySilly", + count: 4, + provider: "bttv", + }, + { + name: "annytfHeart", + count: 98, + provider: "ttv", + }, + { + name: "Catge", + count: 4, + provider: "ffz", + }, ], badges: [adminBadge, botDevBadge], }, @@ -232,11 +257,26 @@ const fakeData: fakeDataEntry[] = [ count: 92, provider: "7tv", }, + { + name: "KissaVei", + count: 1, + provider: "bttv", + }, { name: "SNIFFA", count: 1219, provider: "7tv", }, + { + name: "FeelsBirthdayMan", + count: 1, + provider: "ffz", + }, + { + name: "annytfRave", + count: 5, + provider: "ttv", + }, ], badges: [adminBadge, botDevBadge], }, @@ -252,6 +292,11 @@ const fakeData: fakeDataEntry[] = [ count: 46, provider: "7tv", }, + { + name: "GabeN", + count: 3, + provider: "bttv", + }, { name: "ThisStream", count: 210, @@ -262,6 +307,11 @@ const fakeData: fakeDataEntry[] = [ count: 91, provider: "7tv", }, + { + name: "annytfMelt", + count: 16, + provider: "ttv", + }, ], badges: [CEOBadge, adminBadge], }, @@ -278,9 +328,9 @@ const fakeData: fakeDataEntry[] = [ provider: "7tv", }, { - name: "golive", - count: 90, - provider: "7tv", + name: "annyHop", + count: 61, + provider: "bttv", }, { name: "annyExcitedHug", @@ -292,6 +342,16 @@ const fakeData: fakeDataEntry[] = [ count: 65, provider: "7tv", }, + { + name: "peepoWTF", + count: 60, + provider: "ffz", + }, + { + name: "annytfAngy", + count: 90, + provider: "ttv", + }, ], badges: [adminBadge, botDevBadge], }, @@ -331,6 +391,11 @@ const fakeData: fakeDataEntry[] = [ count: 10, provider: "7tv", }, + { + name: "annySaur", + count: 7, + provider: "bttv", + }, { name: "BAND", count: 49, @@ -341,6 +406,16 @@ const fakeData: fakeDataEntry[] = [ count: 78, provider: "7tv", }, + { + name: "PepegaPhone", + count: 142, + provider: "ffz", + }, + { + name: "annytfHug", + count: 19, + provider: "ttv", + }, ], }, { @@ -356,9 +431,9 @@ const fakeData: fakeDataEntry[] = [ provider: "7tv", }, { - name: "peepoTalk", - count: 73, - provider: "7tv", + name: "annytfLUL", + count: 9, + provider: "ttv", }, { name: "peepoSnow", @@ -371,9 +446,14 @@ const fakeData: fakeDataEntry[] = [ provider: "7tv", }, { - name: "xdd666", - count: 53, - provider: "7tv", + name: "annyBlankies", + count: 88, + provider: "bttv", + }, + { + name: "TWINGO", + count: 98, + provider: "ffz", }, ], }, @@ -394,11 +474,21 @@ const fakeData: fakeDataEntry[] = [ count: 64, provider: "7tv", }, + { + name: "annytfBanana", + count: 15, + provider: "bttv", + }, { name: "ewLeague", count: 64, provider: "7tv", }, + { + name: "annytfPain", + count: 37, + provider: "ttv", + }, ], }, { @@ -418,6 +508,11 @@ const fakeData: fakeDataEntry[] = [ count: 47, provider: "7tv", }, + { + name: "GabeN", + count: 52, + provider: "bttv", + }, { name: "GroupWankge", count: 38, @@ -428,6 +523,11 @@ const fakeData: fakeDataEntry[] = [ count: 90, provider: "7tv", }, + { + name: "annytfKnuckles", + count: 2, + provider: "ttv", + }, ], }, { @@ -447,11 +547,26 @@ const fakeData: fakeDataEntry[] = [ count: 5, provider: "7tv", }, + { + name: "annyPls", + count: 5, + provider: "bttv", + }, { name: "golive", count: 46, provider: "7tv", }, + { + name: "COPIUM", + count: 82, + provider: "ffz", + }, + { + name: "annytfCheer", + count: 54, + provider: "ttv", + }, ], }, { @@ -481,6 +596,16 @@ const fakeData: fakeDataEntry[] = [ count: 13, provider: "7tv", }, + { + name: "annytfBlink", + count: 10, + provider: "bttv", + }, + { + name: "annytfBonk", + count: 77, + provider: "ttv", + }, ], }, { @@ -500,6 +625,16 @@ const fakeData: fakeDataEntry[] = [ count: 100, provider: "7tv", }, + { + name: "annyHop", + count: 16, + provider: "bttv", + }, + { + name: "AndKnuckles", + count: 17, + provider: "ffz", + }, { name: "yoshiJAM", count: 67, @@ -510,6 +645,11 @@ const fakeData: fakeDataEntry[] = [ count: 59, provider: "7tv", }, + { + name: "annytfSit", + count: 53, + provider: "ttv", + }, ], }, { @@ -529,6 +669,11 @@ const fakeData: fakeDataEntry[] = [ count: 82, provider: "7tv", }, + { + name: "annyDFast", + count: 22, + provider: "bttv", + }, { name: "PeepoKittyHug", count: 7, @@ -553,11 +698,21 @@ const fakeData: fakeDataEntry[] = [ count: 62, provider: "7tv", }, + { + name: "annyBlankies", + count: 74, + provider: "bttv", + }, { name: "anyatf", count: 24, provider: "7tv", }, + { + name: "annytfGamba", + count: 32, + provider: "ttv", + }, ], }, { @@ -577,11 +732,21 @@ const fakeData: fakeDataEntry[] = [ count: 61, provider: "7tv", }, + { + name: "Annie", + count: 24, + provider: "bttv", + }, { name: "Lagging", count: 92, provider: "7tv", }, + { + name: "annytfFlower", + count: 33, + provider: "ttv", + }, ], }, { @@ -606,6 +771,16 @@ const fakeData: fakeDataEntry[] = [ count: 7, provider: "7tv", }, + { + name: "annyHopper", + count: 24, + provider: "bttv", + }, + { + name: "annytfFlower", + count: 79, + provider: "ttv", + }, ], }, { @@ -630,6 +805,16 @@ const fakeData: fakeDataEntry[] = [ count: 32, provider: "7tv", }, + { + name: "AngelThump", + count: 41, + provider: "bttv", + }, + { + name: "annytfSad", + count: 2, + provider: "ttv", + }, ], }, { @@ -654,6 +839,16 @@ const fakeData: fakeDataEntry[] = [ count: 99, provider: "7tv", }, + { + name: "annyBlankies", + count: 42, + provider: "bttv", + }, + { + name: "annytfHeart", + count: 63, + provider: "ttv", + }, ], }, ]; diff --git a/pages/api/ffz/emotes.ts b/pages/api/ffz/emotes.ts new file mode 100644 index 0000000..dbc58c9 --- /dev/null +++ b/pages/api/ffz/emotes.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../../misc/redis"; +import { getEmoteSet, getGlobalEmotes } from "../../../misc/FFZAPI"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const redis = createRedisInstance(); + + try { + const channel = req.query.s + ? (await getEmoteSet(redis, req.query.s as string)).set.emoticons + : undefined; + let global = await getGlobalEmotes(redis); + // set global emotes to be the three sets within the global object ("3", "4330") + global = global.sets["3"].emoticons.concat(global.sets["4330"].emoticons); + redis.quit(); + res.status(200).json({ channel, global }); + } catch (e) { + console.log(e); + res + .status(500) + .json({ error: { message: "FFZ or internal API is down", code: 10300 } }); + } +} diff --git a/pages/api/twitch/emotes.ts b/pages/api/twitch/emotes.ts new file mode 100644 index 0000000..9452544 --- /dev/null +++ b/pages/api/twitch/emotes.ts @@ -0,0 +1,30 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { createRedisInstance } from "../../../misc/redis"; +import { getChannelEmotes, getGlobalEmotes } from "../../../misc/TwitchAPI"; + +type Data = { + [key: string]: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const redis = createRedisInstance(); + + try { + const channel = req.query.c + ? (await getChannelEmotes(redis, req.query.c as string)).data + : undefined; + const global = (await getGlobalEmotes(redis)).data; + redis.quit(); + res.status(200).json({ channel, global }); + } catch (e) { + console.log(e); + res + .status(500) + .json({ + error: { message: "Twitch or internal API is down", code: 10100 }, + }); + } +} diff --git a/pages/user/[username]/index.tsx b/pages/user/[username]/index.tsx index cdd3e56..c0057c8 100644 --- a/pages/user/[username]/index.tsx +++ b/pages/user/[username]/index.tsx @@ -9,9 +9,9 @@ import Loading from "../../../components/common/Loading"; // TODO: Animations function UserPage() { - const [channelEmotes, setChannelEmotes] = useState<{ [key: string]: string }>( - {} - ); + const [channelEmotes, setChannelEmotes] = useState<{ + [key: string]: { [key: string]: string }; + }>({}); const [userData, setUserData] = useState<{ [key: string]: any }>({}); const [errorCode, setErrorCode] = useState(null); const router = useRouter(); @@ -36,7 +36,74 @@ function UserPage() { let largest = emote.data.host.files[emote.data.host.files.length - 1]; emotes[emote.data.name] = `https:${base_url}/${largest.name}`; }); - setChannelEmotes(emotes); + // same for global emotes + data.global.namedEmoteSet.emotes.forEach((emote: any) => { + let base_url = emote.data.host.url; + let largest = emote.data.host.files[emote.data.host.files.length - 1]; + emotes[emote.data.name] = `https:${base_url}/${largest.name}`; + }); + // set 7tv key to channelEmotes + setChannelEmotes((prev) => ({ ...prev, "7tv": emotes })); + }); + fetch("/api/bttv/emotes?c=56418014") + .then((res) => res.json()) + .then((data) => { + if (data.error) { + setErrorCode(data.error.code); + return; + } + let emotes: { [key: string]: string } = {}; + data.channel.forEach((emote: any) => { + emotes[emote.code] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; + }); + data.global.forEach((emote: any) => { + emotes[emote.code] = `https://cdn.betterttv.net/emote/${emote.id}/3x`; + }); + // add as bttv key to channelEmotes + setChannelEmotes((prev) => ({ ...prev, bttv: emotes })); + }); + fetch("/api/ffz/emotes?s=341402") + .then((res) => res.json()) + .then((data) => { + if (data.error) { + setErrorCode(data.error.code); + return; + } + let emotes: { [key: string]: string } = {}; + data.channel.forEach((emote: any) => { + // ffz emotes don't have all sizes available, so we need to get the largest one by taking the largest key in the urls object + emotes[emote.name] = `https:${ + emote.urls[ + Math.max(...Object.keys(emote.urls).map((k) => parseInt(k))) + ] + }`; + }); + data.global.forEach((emote: any) => { + emotes[emote.name] = `https:${ + emote.urls[ + Math.max(...Object.keys(emote.urls).map((k) => parseInt(k))) + ] + }`; + }); + // add as ffz key to channelEmotes + setChannelEmotes((prev) => ({ ...prev, ffz: emotes })); + }); + fetch("/api/twitch/emotes?c=56418014") + .then((res) => res.json()) + .then((data) => { + if (data.error) { + setErrorCode(data.error.code); + return; + } + let emotes: { [key: string]: string } = {}; + data.channel.forEach((emote: any) => { + emotes[emote.name] = emote.images["url_4x"]; + }); + data.global.forEach((emote: any) => { + emotes[emote.name] = emote.images["url_4x"]; + }); + // add as twitch key to channelEmotes + setChannelEmotes((prev) => ({ ...prev, ttv: emotes })); }); fetch(`/api/fakeUsers?u=${username}`) .then((res) => res.json()) @@ -53,6 +120,8 @@ function UserPage() { // 20000 = user not found // 10000 = 7tv api error // 10100 = Twitch api error + // 10200 = BTTV api error + // 10300 = FFZ api error const errorMsg = errorCode === 20000 ? "User not found" : "API error"; return ( ); } - + console.log(channelEmotes); return ( <> @@ -100,6 +169,7 @@ function UserPage() { alt="User avatar" width={140} height={140} + priority className="absolute rounded-lg border-4" style={{ borderColor: userData.badges[0] @@ -217,14 +287,21 @@ function UserPage() { >
-
+
{ - // if error code is 10000, show placeholder image - errorCode === 10000 ? ( + // if error code is 10000 or emote does not exist, show placeholder image + errorCode === 10000 || + channelEmotes[asset.provider] === undefined || + channelEmotes[asset.provider][asset.name] === + undefined ? (

{`404 :(`}

) : ( {asset.name}
{ - // show provider logo (7tv, bttv, ffz, twitch) + // show provider logo (7tv, bttv, ffz, ttv) asset.provider === "7tv" ? (