From a6f29b2e58b9b3ae630ec72652839f7192a141e3 Mon Sep 17 00:00:00 2001 From: Matt Hughes Date: Fri, 4 Aug 2023 15:06:36 -0700 Subject: [PATCH 1/3] necro --- services/stats/main.go | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/services/stats/main.go b/services/stats/main.go index 48c0658..473adc5 100644 --- a/services/stats/main.go +++ b/services/stats/main.go @@ -43,9 +43,15 @@ func generateSkillQuery(skill string, skillId int, playerId int) string { } func main() { + var dryRun = false rootPassword := os.Getenv("MYSQL_ROOT_PASSWORD") + var officialApiUrl = "https://secure.runescape.com/m=hiscore/index_lite.ws?player=" var connectionString = "root:" + rootPassword + "@tcp(containers-us-west-150.railway.app:7266)/railway" + + // will be overwritten with necro + var numSkills = 29 + db, err := sql.Open("mysql", connectionString) if err != nil { panic(err.Error()) @@ -55,7 +61,12 @@ func main() { defer db.Close() // query the database for the players to update - results, err := db.Query("SELECT id, username FROM Player where isTracking = true") + var queryString = "SELECT id, username FROM Player where isTracking = true" + if dryRun { + queryString = "SELECT id, username FROM Player where isTracking = true and id = 1" + } + + results, err := db.Query(queryString) if err != nil { panic(err.Error()) } @@ -112,7 +123,14 @@ func main() { // skill insert var skillQuery strings.Builder skillQuery.WriteString("insert into Skill values ") - for j := 0; j < 29; j++ { + + // check if necro is in skill list + if len(splitResponse[29]) == 3 { + // add 1 to numSkills + numSkills = numSkills + 1 + } + + for j := 0; j < numSkills; j++ { if splitResponse[j] != "" { skillQuery.WriteString(generateSkillQuery(splitResponse[j], j, playerId)) if j != 28 { @@ -123,15 +141,17 @@ func main() { } } - _, err = db.Exec(skillQuery.String()) - if err != nil { - panic(err.Error()) + if !dryRun { + _, err = db.Exec(skillQuery.String()) + if err != nil { + panic(err.Error()) + } } // minigame insert var minigameQuery strings.Builder minigameQuery.WriteString("insert into Minigame values ") - for j := 29; j < len(splitResponse); j++ { + for j := numSkills; j < len(splitResponse); j++ { if splitResponse[j] != "" { minigameQuery.WriteString(generateMinigameQuery(splitResponse[j], j, playerId)) if j != len(splitResponse)-2 { @@ -142,9 +162,11 @@ func main() { } } - _, err = db.Exec(minigameQuery.String()) - if err != nil { - panic(err.Error()) + if !dryRun { + _, err = db.Exec(minigameQuery.String()) + if err != nil { + panic(err.Error()) + } } log := fmt.Sprintf("> created for: %s", players[i].Username) From 5ce601fd6181b08cef14cdec1d7fcd4eee3c2c5f Mon Sep 17 00:00:00 2001 From: Matt Hughes Date: Sun, 6 Aug 2023 20:50:14 -0700 Subject: [PATCH 2/3] necro --- services/activities/main.go | 2 + src/components/ActivityList.tsx | 21 ++++++- src/components/NecroList.tsx | 93 ++++++++++++++++++++++++++++++ src/components/TopDxpList.tsx | 2 +- src/pages/index.tsx | 70 ++++++++++++---------- src/pages/rs3/[username].tsx | 1 + src/server/common/stat-services.ts | 37 ++++++++++++ src/server/trpc/router/player.ts | 10 ++++ src/types/user-types.ts | 15 +++++ src/utils/constants.ts | 28 +++++++++ 10 files changed, 244 insertions(+), 35 deletions(-) create mode 100644 src/components/NecroList.tsx diff --git a/services/activities/main.go b/services/activities/main.go index 3bf7008..e14a812 100644 --- a/services/activities/main.go +++ b/services/activities/main.go @@ -249,6 +249,8 @@ func main() { {"Bandos shield", "Bandos warshield"}, {"Scriptures", "Scripture"}, {"Erethdor's grimoire", "Erethdor's grimoire (token)"}, + {"Dragonrider Lance", "Dragon Rider lance"}, + {"Wand Of The Cwyir Elders", "Wand Of The Cywir Elders"}, } rootPassword := os.Getenv("MYSQL_ROOT_PASSWORD") diff --git a/src/components/ActivityList.tsx b/src/components/ActivityList.tsx index ed2adba..609c9aa 100644 --- a/src/components/ActivityList.tsx +++ b/src/components/ActivityList.tsx @@ -6,6 +6,7 @@ import { skillNameArray, skillIcon } from "../utils/constants"; import type { StaticImageData } from "next/image"; import Avatar from "./Avatar"; import Coins from "../assets/images/coins.png"; +import LoadingSpinner from "./LoadingSpinner"; const formatDate = (date: string): string => { // date format: 21-Jan-2023 00:31 @@ -107,7 +108,7 @@ const formatActivity = (

{activity.username.split("+").join(" ")}

- + )} @@ -118,21 +119,35 @@ type ActivityListProps = { activities: Activity[]; username?: string; title?: string; + loading?: boolean; }; -const ActivityList = ({ activities, username, title }: ActivityListProps) => { +const ActivityList = ({ + activities, + username, + title, + loading, +}: ActivityListProps) => { const router = useRouter(); return (

{title ?? "Activities"}

- {activities.length > 0 ? ( + + {loading && ( +
+ +
+ )} + {activities.length > 0 && !loading ? (
{activities.map((activity: Activity, i: number) => formatActivity(activity, i, !username, router) )}
+ ) : loading ? ( +
) : (

{`${username}'s RuneMetrics profile is set to private.`} diff --git a/src/components/NecroList.tsx b/src/components/NecroList.tsx new file mode 100644 index 0000000..d4f9f55 --- /dev/null +++ b/src/components/NecroList.tsx @@ -0,0 +1,93 @@ +import { useRouter } from "next/router"; +import type { TopRankedPlayer } from "../types/user-types"; +import { skillIcon, skillNameArray } from "../utils/constants"; +import Avatar from "./Avatar"; +import { trpc } from "../utils/trpc"; +import LoadingSpinner from "./LoadingSpinner"; + +const formatPlayer = ( + player: TopRankedPlayer, + i: number, + skillId: number, + router?: ReturnType +) => { + return ( +

+
+
{i + 1}
+
router?.push(`/rs3/${player.username}`)} + className="flex w-9/12 cursor-pointer flex-col items-center hover:underline xl:flex-row" + > + +

+ {player.displayName} +

+
+
+
+
+

+ {player.xp.toLocaleString()} xp +

+ {player.level && ( +

+ Level {player.level} +

+ )} +
+ skill icon +
+
+ ); +}; + +type TopDxpListProps = { + skillId: number; +}; + +const NecroList = ({ skillId }: TopDxpListProps) => { + const router = useRouter(); + const title = `Top ${skillNameArray[skillId]} Players`; + + const { isFetching, data } = trpc.player.getTopNecroPlayers.useQuery( + undefined, + { + refetchOnMount: true, + } + ); + + const players = data?.find((d) => d.skillId === skillId)?.players ?? []; + + return ( +
+

+ {title} +

+ {!isFetching && players.length > 0 ? ( +
+ {players.map((player: TopRankedPlayer, i: number) => + formatPlayer(player, i, skillId, router) + )} +
+ ) : ( +
+ +
+ )} +
+ ); +}; + +export default NecroList; diff --git a/src/components/TopDxpList.tsx b/src/components/TopDxpList.tsx index 0bc1fd5..ef5e04a 100644 --- a/src/components/TopDxpList.tsx +++ b/src/components/TopDxpList.tsx @@ -23,7 +23,7 @@ const formatPlayer = ( onClick={() => router?.push(`/rs3/${player.username}`)} className="flex w-9/12 cursor-pointer flex-col items-center hover:underline xl:flex-row" > - +

{player.displayName}

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a7f9535..39abef5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,7 +8,12 @@ import ActivityList from "../components/ActivityList"; import LoadingSpinner from "../components/LoadingSpinner"; import type { Activity, TopPlayer } from "../types/user-types"; import TopDxpList from "../components/TopDxpList"; -import { isCurrentlyDxp } from "../utils/constants"; +import NecroList from "../components/NecroList"; +import { + isCurrentlyDxp, + necroReleased, + TotalSkillsRs3, +} from "../utils/constants"; const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_MEASUREMENT_ID; const Home: NextPageWithLayout = () => { @@ -29,9 +34,9 @@ const Home: NextPageWithLayout = () => { const { isFetching: isXpFetching } = getGains.useQuery(undefined, { refetchOnMount: true, onSuccess: (data) => setXpData(data), + enabled: false, }); - const showActivities = activities && activities.length > 0; const handleSearch = (e: any) => { setLoading(true); e.preventDefault(); @@ -64,41 +69,44 @@ const Home: NextPageWithLayout = () => { `}
-
-
+
+
{isXpFetching && !xpData && ( )} {xpData && } +
-
-

- Woodcut -

-
- setSearch(e.target.value)} - /> - -
+
+
-
- {isFetching && !activities && ( - - )} - {showActivities && } + {false && ( +
+

+ Woodcut +

+
+ setSearch(e.target.value)} + /> + +
+
+ )} +
+
diff --git a/src/pages/rs3/[username].tsx b/src/pages/rs3/[username].tsx index e7cb34c..4e426a6 100644 --- a/src/pages/rs3/[username].tsx +++ b/src/pages/rs3/[username].tsx @@ -35,6 +35,7 @@ const Rs3: NextPageWithLayout = () => { refetchIntervalInBackground: false, onError: (error) => setError(error.data?.code ?? ""), onSuccess: () => setError(""), + onSettled: () => setLoadingMessage(""), } ); diff --git a/src/server/common/stat-services.ts b/src/server/common/stat-services.ts index 1d6d12a..a5195e5 100644 --- a/src/server/common/stat-services.ts +++ b/src/server/common/stat-services.ts @@ -18,6 +18,9 @@ import { RuneScoreId, MaxRuneScore, necroReleased, + RunescapeApiRankedUrlRs3Pre, + RunescapeApiRankedUrlRs3Post, + xpToLevel, } from "../../utils/constants"; import type { Activity, @@ -27,6 +30,8 @@ import type { Progress, Skill, TopPlayer, + TopRankedPlayer, + TopRankedPlayerRaw, } from "../../types/user-types"; import { formatActivity, @@ -118,6 +123,21 @@ const createStatRecordFromData = (data: string[]) => { }; }; +const officialRankedApiCall = async ( + skillId: number +): Promise => { + const data = await fetch( + `${RunescapeApiRankedUrlRs3Pre}${skillId}${RunescapeApiRankedUrlRs3Post}` + ) + .then((res) => res.json()) + .catch((err) => { + console.log(err); + return null; + }); + + return data; +}; + const officialApiCall = async (username: string): Promise => { if (username === "test") { return TestData.split(" ").join("\n"); @@ -758,3 +778,20 @@ const getBadges = (minigames: Minigame[], milestones: Progress[]) => { return resp; }; + +export const getTopRankedPlayers = async (skillId: number) => { + const rawData = await officialRankedApiCall(skillId); + const players: TopRankedPlayer[] = rawData.map((player) => { + const xp = Number(player.score.split(",").join("")); + + return { + username: player.name.toLowerCase().split(" ").join("+"), + displayName: player.name, + xp, + level: skillId > 0 ? xpToLevel(xp) : undefined, + rank: Number(player.rank), + }; + }); + + return { skillId, players: players.sort((a, b) => a.rank - b.rank) }; +}; diff --git a/src/server/trpc/router/player.ts b/src/server/trpc/router/player.ts index 7ad198d..d87f3ae 100644 --- a/src/server/trpc/router/player.ts +++ b/src/server/trpc/router/player.ts @@ -6,8 +6,10 @@ import { getPlayerData, getTopDxpPlayers, getTopPlayersInDateRange, + getTopRankedPlayers, } from "../../common/stat-services"; import { getFormattedActivities } from "../../common/activity-services"; +import { TotalSkillsRs3 } from "../../../utils/constants"; export const playerRouter = router({ getPlayerStats: publicProcedure @@ -57,4 +59,12 @@ export const playerRouter = router({ const activities = await getFormattedActivities({ ctx, limit: 250 }); return activities; }), + getTopNecroPlayers: publicProcedure.query(async () => { + const [overall, necromancy] = await Promise.all([ + getTopRankedPlayers(0), + getTopRankedPlayers(TotalSkillsRs3 - 1), + ]); + + return [overall, necromancy]; + }), }); diff --git a/src/types/user-types.ts b/src/types/user-types.ts index f1825df..7a0137f 100644 --- a/src/types/user-types.ts +++ b/src/types/user-types.ts @@ -84,6 +84,21 @@ export type TopPlayer = { gain: number; }; +export type TopRankedPlayerRaw = { + name: string; + score: string; + rank: string; +}; + +export type TopRankedPlayer = { + id?: number; + username: string; + displayName: string; + xp: number; + level?: number; + rank: number; +}; + export type BadgeId = | "max" | "maxTotal" diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b8e3036..5a30fd3 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -15,6 +15,9 @@ export const RunescapeImApiBaseUrlRs3 = "https://secure.runescape.com/m=hiscore_ironman/index_lite.ws?player="; export const RunescapeHcimApiBaseUrlRs3 = "https://secure.runescape.com/m=hiscore_hardcore_ironman/index_lite.ws?player="; +export const RunescapeApiRankedUrlRs3Pre = + "https://secure.runescape.com/m=hiscore/ranking.json?table="; +export const RunescapeApiRankedUrlRs3Post = "&category=0&size=50"; export const RunescapeApiBaseUrlOsrs = "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player="; @@ -293,6 +296,31 @@ export const textToIgnore = [ export const detailsToIgnore = [...levelsToIgnore]; // Other +const xpTable = [ + 0, 83, 174, 276, 388, 512, 650, 801, 969, 1154, 1358, 1584, 1833, 2107, 2411, + 2746, 3115, 3523, 3973, 4470, 5018, 5624, 6291, 7028, 7842, 8740, 9730, 10824, + 12031, 13363, 14833, 16456, 18247, 20224, 22406, 24815, 27473, 30408, 33648, + 37224, 41171, 45529, 50339, 55649, 61512, 67983, 75127, 83014, 91721, 101333, + 111945, 123660, 136594, 150872, 166636, 184040, 203254, 224466, 247886, + 273742, 302288, 333804, 368599, 407015, 449428, 496254, 547953, 605032, + 668051, 737627, 814445, 899257, 992895, 1096278, 1210421, 1336443, 1475581, + 1629200, 1798808, 1986068, 2192818, 2421087, 2673114, 2951373, 3258594, + 3597792, 3972294, 4385776, 4842295, 5346332, 5902831, 6517253, 7195629, + 7944614, 8771558, 9684577, 10692629, 11805606, 13034431, 14391160, 15889109, + 17542976, 19368992, 21385073, 23611006, 26068632, 28782069, 31777943, + 35085654, 38737661, 42769801, 47221641, 52136869, 57563718, 63555443, + 70170840, 77474828, 85539082, 94442737, 104273167, +]; + +export const xpToLevel = (xp: number) => { + for (let i = xpTable.length - 1; i >= 0; i--) { + if (xp >= xpTable[i]) { + return i + 1; + } + } + + return 1; // Return level 1 if XP is lower than the minimum value in the table +}; export const verificationWorlds = [ 7, 8, 11, 17, 19, 20, 29, 34, 38, 41, 43, 55, 61, 80, 81, 94, 108, 141, From c9385b89242b8f63196c74e82090b7b7fdb9d014 Mon Sep 17 00:00:00 2001 From: Matt Hughes Date: Sun, 6 Aug 2023 21:23:55 -0700 Subject: [PATCH 3/3] avatar image retry --- src/components/Avatar.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 34a09eb..d771632 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,9 +1,11 @@ +import React, { useState } from "react"; type props = { username: string; width?: string; }; const Avatar = ({ username, width = "w-full" }: props) => { + const [imgKey, setImgKey] = useState(0); const formattedUsername = username.split(" ").join("+"); const url = `https://secure.runescape.com/m=avatar-rs/${formattedUsername}/chat.png`; @@ -13,11 +15,16 @@ const Avatar = ({ username, width = "w-full" }: props) => { return ( avatar { currentTarget.onerror = null; // prevents looping currentTarget.src = backupUrl; + if (imgKey === 0) { + currentTarget.src = url; + setImgKey(1); + } }} /> );