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..612004c 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; @@ -26,6 +25,7 @@ function DashLayout(props: DashLayoutProps) { variants={containerVariants} > + Dashboard - toffee 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/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/misc/TwitchAPI.tsx b/misc/TwitchAPI.tsx new file mode 100644 index 0000000..ab16d86 --- /dev/null +++ b/misc/TwitchAPI.tsx @@ -0,0 +1,94 @@ +import 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 }; 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/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/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/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 new file mode 100644 index 0000000..85fe5e9 --- /dev/null +++ b/pages/api/fakePrices.ts @@ -0,0 +1,804 @@ +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, + // 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/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..052e9dc --- /dev/null +++ b/pages/api/fakeUsers.ts @@ -0,0 +1,856 @@ +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 + 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 + 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: "7tv", + }, + { + name: "HUH", + count: 1, + provider: "7tv", + }, + { + name: "annytfSigh", + count: 1, + provider: "ttv", + }, + { + name: "GabeN", + count: 3, + provider: "bttv", + }, + { + name: "widepeepoBlanket", + 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", + }, + { + name: "AnnySilly", + count: 4, + provider: "bttv", + }, + { + name: "annytfHeart", + count: 98, + provider: "ttv", + }, + { + name: "Catge", + count: 4, + provider: "ffz", + }, + ], + 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: "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], + }, + { + id: 3, + name: "SecondSockSan", + points: 15020, + daily_change: -10432, + daily_change_percent: -0.06796312583471774324896576435715, + assets: [ + { + name: "AYAYAjam", + count: 46, + provider: "7tv", + }, + { + name: "GabeN", + count: 3, + provider: "bttv", + }, + { + name: "ThisStream", + count: 210, + provider: "7tv", + }, + { + name: "BAND", + count: 91, + provider: "7tv", + }, + { + name: "annytfMelt", + count: 16, + provider: "ttv", + }, + ], + 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: "annyHop", + count: 61, + provider: "bttv", + }, + { + name: "annyExcitedHug", + count: 26, + provider: "7tv", + }, + { + name: "AAAA", + count: 65, + provider: "7tv", + }, + { + name: "peepoWTF", + count: 60, + provider: "ffz", + }, + { + name: "annytfAngy", + count: 90, + provider: "ttv", + }, + ], + 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: "annySaur", + count: 7, + provider: "bttv", + }, + { + name: "BAND", + count: 49, + provider: "7tv", + }, + { + name: "SNIFFA", + count: 78, + provider: "7tv", + }, + { + name: "PepegaPhone", + count: 142, + provider: "ffz", + }, + { + name: "annytfHug", + count: 19, + provider: "ttv", + }, + ], + }, + { + id: 7, + name: "joeeyo", + points: 99979, + daily_change: 1, + daily_change_percent: 0.0000001, + assets: [ + { + name: "Siti", + count: 32, + provider: "7tv", + }, + { + name: "annytfLUL", + count: 9, + provider: "ttv", + }, + { + name: "peepoSnow", + count: 37, + provider: "7tv", + }, + { + name: "MadgeJuice", + count: 70, + provider: "7tv", + }, + { + name: "annyBlankies", + count: 88, + provider: "bttv", + }, + { + name: "TWINGO", + count: 98, + provider: "ffz", + }, + ], + }, + { + 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: "annytfBanana", + count: 15, + provider: "bttv", + }, + { + name: "ewLeague", + count: 64, + provider: "7tv", + }, + { + name: "annytfPain", + count: 37, + provider: "ttv", + }, + ], + }, + { + 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: "GabeN", + count: 52, + provider: "bttv", + }, + { + name: "GroupWankge", + count: 38, + provider: "7tv", + }, + { + name: "annyCucumber", + count: 90, + provider: "7tv", + }, + { + name: "annytfKnuckles", + count: 2, + provider: "ttv", + }, + ], + }, + { + 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: "annyPls", + count: 5, + provider: "bttv", + }, + { + name: "golive", + count: 46, + provider: "7tv", + }, + { + name: "COPIUM", + count: 82, + provider: "ffz", + }, + { + name: "annytfCheer", + count: 54, + provider: "ttv", + }, + ], + }, + { + 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", + }, + { + name: "annytfBlink", + count: 10, + provider: "bttv", + }, + { + name: "annytfBonk", + count: 77, + provider: "ttv", + }, + ], + }, + { + 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: "annyHop", + count: 16, + provider: "bttv", + }, + { + name: "AndKnuckles", + count: 17, + provider: "ffz", + }, + { + name: "yoshiJAM", + count: 67, + provider: "7tv", + }, + { + name: "WhoAsked", + count: 59, + provider: "7tv", + }, + { + name: "annytfSit", + count: 53, + provider: "ttv", + }, + ], + }, + { + 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: "annyDFast", + count: 22, + provider: "bttv", + }, + { + 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: "annyBlankies", + count: 74, + provider: "bttv", + }, + { + name: "anyatf", + count: 24, + provider: "7tv", + }, + { + name: "annytfGamba", + count: 32, + provider: "ttv", + }, + ], + }, + { + 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: "Annie", + count: 24, + provider: "bttv", + }, + { + name: "Lagging", + count: 92, + provider: "7tv", + }, + { + name: "annytfFlower", + count: 33, + provider: "ttv", + }, + ], + }, + { + 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", + }, + { + name: "annyHopper", + count: 24, + provider: "bttv", + }, + { + name: "annytfFlower", + count: 79, + provider: "ttv", + }, + ], + }, + { + 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", + }, + { + name: "AngelThump", + count: 41, + provider: "bttv", + }, + { + name: "annytfSad", + count: 2, + provider: "ttv", + }, + ], + }, + { + 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", + }, + { + name: "annyBlankies", + count: 42, + provider: "bttv", + }, + { + name: "annytfHeart", + count: 63, + provider: "ttv", + }, + ], + }, +]; + +export type { fakeDataEntry }; 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/index.tsx b/pages/index.tsx index 197ffd0..25bd22e 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); @@ -15,6 +14,10 @@ const Home: NextPageWithLayout = () => { 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) => { @@ -79,49 +82,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 +142,7 @@ const sloganContainerVariants = { bounce: 0.5, stiffness: 150, delayChildren: 1.0, - staggerChildren: 0.45, + staggerChildren: 0.1, }, }, }; @@ -171,9 +150,11 @@ const sloganContainerVariants = { const sloganHeaderVariants = { initial: { opacity: 0, + y: -15, }, animate: { opacity: 1, + y: 0, }, }; diff --git a/pages/dashboard/ranking.tsx b/pages/ranking/index.tsx similarity index 74% rename from pages/dashboard/ranking.tsx rename to pages/ranking/index.tsx index 832718d..5c6bb14 100644 --- a/pages/dashboard/ranking.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..c0057c8 --- /dev/null +++ b/pages/user/[username]/index.tsx @@ -0,0 +1,486 @@ +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]: { [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}`; + }); + // 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()) + .then((data) => { + if (data.error) { + setErrorCode(data.error.code); + } + setUserData(data.data); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady]); + + if (errorCode !== null) { + // 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 ( + +

{errorMsg}

+
+ ); + } + + // if json is empty, and if channelEmotes is incomplete, show loading screen + if ( + Object.keys(channelEmotes).length < 4 || + !userData || + Object.keys(userData).length === 0 + ) { + return ( +
+ +
+ ); + } + console.log(channelEmotes); + 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 +
+
+

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

+
+
+
+ {/* 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 or emote does not exist, show placeholder image + errorCode === 10000 || + channelEmotes[asset.provider] === undefined || + channelEmotes[asset.provider][asset.name] === + undefined ? ( +

{`404 :(`}

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

+ x{asset.count} +

+
+
+
+
+ { + // show provider logo (7tv, bttv, ffz, ttv) + 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 0000000..39d254f Binary files /dev/null and b/public/img/well_drawn_rank_chart.webp differ 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: [],