Compare commits

...

4 Commits

Author SHA1 Message Date
Hri7566 919e2ff7ad Update README.md 2024-10-11 19:08:35 -04:00
Hri7566 1a8a272c87 Merge branch 'dev' 2024-09-23 18:17:03 -04:00
Hri7566 ee3629faa6 stuff 2024-09-15 18:19:17 -04:00
Hri7566 0456fd9226 dev config 2024-09-15 08:08:51 -04:00
16 changed files with 181 additions and 71 deletions

View File

@ -1,3 +1,5 @@
<img src="./fish_icon.png" width="64" alt="Crucian Carp" />
# fishing-api # fishing-api
This is a rewrite of Brandon Lockaby's fishing bot for Multiplayer Piano. This is a rewrite of Brandon Lockaby's fishing bot for Multiplayer Piano.
@ -16,14 +18,16 @@ Copy the default `.env` file:
cp .env.template .env cp .env.template .env
``` ```
Edit that file to match your environment, then install packages: Edit that file to match your environment. Keep in mind that a connection token is required for all fishing service clients. These clients are for connecting to the actual service (such as Discord or MPP) and bridges the connection to the backend API. This way, the execution related to fishing itself is focused in its own process, instead of travelling between sending messages and processing fishing-related computations.
Next, install the project's dependencies from npm:
```bash ```bash
bun install bun install
bunx prisma db push bunx prisma db push
``` ```
Run both the http server and the clients for various services separately with these commands: Run both the HTTP API and the clients for various services separately with these commands:
```bash ```bash
bun . # Main http server bun . # Main http server

BIN
bun.lockb

Binary file not shown.

View File

@ -2,7 +2,3 @@
channel: channel:
id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧" id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧"
allowColorChanging: true allowColorChanging: true
- uri: wss://mppclone.com:8443
channel:
id: "test/fishing"
allowColorChanging: true

BIN
fish_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -8,22 +8,22 @@
"start-discord": "bun src/discord/index.ts" "start-discord": "bun src/discord/index.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "^1.1.6" "@types/bun": "^1.1.9"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^5.16.1", "@prisma/client": "^5.19.1",
"@trpc/client": "next", "@trpc/client": "next",
"@trpc/server": "next", "@trpc/server": "next",
"@types/node": "^20.14.10", "@types/node": "^20.16.5",
"cli-markdown": "^3.4.0", "cli-markdown": "^3.4.0",
"discord.js": "^14.15.3", "discord.js": "^14.16.2",
"mpp-client-net": "^1.2.0", "mpp-client-net": "^1.2.3",
"prisma": "^5.16.1", "prisma": "^5.19.1",
"trpc-bun-adapter": "^1.1.1", "trpc-bun-adapter": "^1.1.2",
"yaml": "^2.4.5", "yaml": "^2.5.1",
"zod": "^3.23.8" "zod": "^3.23.8"
} }
} }

View File

@ -25,6 +25,7 @@ import { fid } from "./util/fid";
import { chance } from "./util/chance"; import { chance } from "./util/chance";
import { info } from "./general/info"; import { info } from "./general/info";
import { burger } from "./util/burger"; import { burger } from "./util/burger";
import { daily } from "./pokemon/daily";
// import { give } from "./inventory/give"; // import { give } from "./inventory/give";
interface ICommandGroup { interface ICommandGroup {
@ -54,15 +55,23 @@ commandGroups.push(fishingGroup);
const inventoryGroup: ICommandGroup = { const inventoryGroup: ICommandGroup = {
id: "inventory", id: "inventory",
displayName: "Inventory", displayName: "Inventory",
commands: [inventory, take, eat, sack, pokemon, yeet, burger /* give */] commands: [inventory, sack, pokemon, take, eat, yeet, burger /* give */]
}; };
commandGroups.push(inventoryGroup); commandGroups.push(inventoryGroup);
const pokemonGroup: ICommandGroup = {
id: "pokemon",
displayName: "Pokémon",
commands: [daily, pokedex]
};
commandGroups.push(pokemonGroup);
const utilGroup: ICommandGroup = { const utilGroup: ICommandGroup = {
id: "util", id: "util",
displayName: "Utility", displayName: "Utility",
commands: [data, setcolor, memory, autofish, pokedex, fid, chance] commands: [data, setcolor, memory, autofish, fid, chance]
}; };
commandGroups.push(utilGroup); commandGroups.push(utilGroup);

View File

@ -1,5 +1,7 @@
import Command from "@server/commands/Command"; import Command from "@server/commands/Command";
import { logger } from "@server/commands/handler";
import { getInventory, updateInventory } from "@server/data/inventory"; import { getInventory, updateInventory } from "@server/data/inventory";
import { removeItem } from "@server/items";
import { itemBehaviorMap, runBehavior } from "@server/items/behavior"; import { itemBehaviorMap, runBehavior } from "@server/items/behavior";
export const eat = new Command( export const eat = new Command(
@ -62,45 +64,9 @@ export const eat = new Command(
if (shouldRemove) { if (shouldRemove) {
if (foundObject.objtype == "fish") { if (foundObject.objtype == "fish") {
i = 0; removeItem(inventory.fishSack, foundObject);
for (const fish of inventory.fishSack as TFishSack) {
if (typeof fish.count !== "undefined") {
if (fish.count > 1) {
shouldRemove = false;
((inventory.fishSack as TFishSack)[i]
.count as number)--;
} else {
shouldRemove = true;
}
} else {
shouldRemove = true;
}
if (shouldRemove)
(inventory.fishSack as TFishSack).splice(i, 1);
break;
}
} else if (foundObject.objtype == "item") { } else if (foundObject.objtype == "item") {
i = 0; removeItem(inventory.items, foundObject);
for (const item of inventory.items as unknown as IItem[]) {
if (typeof item.count == "number") {
if (item.count > 1) {
shouldRemove = false;
((inventory.items as TInventoryItems)[i]
.count as number)--;
} else {
shouldRemove = true;
}
} else {
shouldRemove = true;
}
if (shouldRemove)
(inventory.items as TInventoryItems).splice(i, 1);
break;
}
} }
await updateInventory(inventory); await updateInventory(inventory);

View File

@ -0,0 +1,21 @@
import Command from "@server/commands/Command";
import { logger } from "@server/commands/handler";
import { claimDailyPokemon } from "@server/pokemon/daily";
export const daily = new Command(
"daily",
["daily", "dailypokemon"],
"Claim your daily Pokémon reward",
"daily",
"command.inventory.daily",
async ({ id, command, args, prefix, part, user }) => {
try {
const message = await claimDailyPokemon(user.id);
return message;
} catch (err) {
logger.error("Unable to perform daily claim:", err);
return `Congratulations, you broke the bot. Your daily reward might not work now.`;
}
},
true
);

View File

@ -1,15 +1,16 @@
import type { User } from "@prisma/client";
import prisma from "./prisma"; import prisma from "./prisma";
export async function createInventory(inventory: Partial<IInventory>) { export async function createInventory(inventory: Partial<IInventory>) {
return await prisma.inventory.create({ return (await prisma.inventory.create({
data: inventory data: inventory
}); })) as IInventory;
} }
export async function getInventory(id: IInventory["id"]) { export async function getInventory(id: IInventory["id"]) {
return await prisma.inventory.findUnique({ return (await prisma.inventory.findUnique({
where: { id } where: { id }
}); })) as IInventory;
} }
export async function updateInventory(inventory: Partial<IInventory>) { export async function updateInventory(inventory: Partial<IInventory>) {

View File

@ -108,7 +108,11 @@ export function hasFishTime(
export function getSizeString(cm: number) { export function getSizeString(cm: number) {
const size = const size =
cm < 30 cm < 5
? "microscopic"
: cm < 10
? "tiny"
: cm < 30
? "small" ? "small"
: cm < 60 : cm < 60
? "medium-sized" ? "medium-sized"

View File

@ -23,3 +23,26 @@ export function addItem(arr: IObject[], item: IObject) {
(arr[i].count as number) += inc; (arr[i].count as number) += inc;
} }
} }
export function removeItem(arr: IObject[], item: IObject, count = 1) {
let found = false;
let i = 0;
for (i = 0; i < arr.length; i++) {
if (item.id == arr[i].id) {
found = true;
break;
}
}
const foundItem = arr[i];
if (!found || !foundItem) return false;
if (typeof foundItem.count == "number" && foundItem.count > 1) {
foundItem.count -= count;
} else {
arr.splice(i, 1);
}
return true;
}

62
src/api/pokemon/daily.ts Normal file
View File

@ -0,0 +1,62 @@
import { kvGet, kvSet } from "@server/data/keyValueStore";
import { Logger } from "@util/Logger";
import { getRandomPokemon } from "./pokedex";
import { getInventory, updateInventory } from "@server/data/inventory";
import { getUser } from "@server/data/user";
import { addItem } from "@server/items";
import { getHHMMSS } from "@util/time";
const logger = new Logger("Daily Pokemon Manager");
const oneDay = 1000 * 60 * 60 * 24;
export async function claimDailyPokemon(userID: string) {
// Get the last daily timestamp
let timestampS = await kvGet(`pokedaily~${userID}`);
let timestamp = 0;
if (typeof timestampS == "string") {
try {
timestamp = parseInt(timestampS);
} catch (err) {
logger.warn("Unable to parse JSON:", err);
}
}
logger.debug("Time remaining:", Date.now() - timestamp);
// Check if it has been over a day
if (Date.now() - timestamp > oneDay) {
// Give them a random pokemon and set new timestamp
const pokemon = getRandomPokemon();
const item = {
id: pokemon.pokeID.toString(),
name: pokemon.name,
pokeID: pokemon.pokeID,
base: pokemon.base,
type: pokemon.type,
count: 1,
objtype: "pokemon"
} as IPokemon;
const user = await getUser(userID);
if (!user) throw new Error("No user found");
const inventory = await getInventory(user.inventoryId);
if (!inventory) throw new Error("No inventory found");
addItem(inventory.pokemon, pokemon);
await updateInventory(inventory);
kvSet(`pokedaily~${userID}`, Date.now().toString());
return `You claimed your daily Pokémon reward and got: ${
item.emoji || "📦"
}${item.name}${item.count ? ` (x${item.count})` : ""}`;
} else {
// or tell them no
return `You already claimed today! Time remaining: ${getHHMMSS(
oneDay - (Date.now() - timestamp),
false
)}`;
}
}

View File

@ -8,3 +8,8 @@ const logger = new Logger("Pokédex");
export function getPokemonByID(id: number) { export function getPokemonByID(id: number) {
return pokedex.find(p => p.pokeID == id); return pokedex.find(p => p.pokeID == id);
} }
export function getRandomPokemon() {
const r = Math.floor(Math.random() * pokedex.length);
return pokedex[r];
}

View File

@ -1,8 +1,9 @@
import { Logger } from "@util/Logger"; import { Logger } from "@util/Logger";
import { createInterface } from "readline"; import { createInterface } from "readline";
import { EventEmitter } from "events"; import { EventEmitter } from "node:events";
import gettRPC from "@util/api/trpc"; import gettRPC from "@util/api/trpc";
import { startAutorestart } from "@util/autorestart"; import { startAutorestart } from "@util/autorestart";
import { CosmicColor } from "@util/CosmicColor";
const trpc = gettRPC(process.env.CLI_FISHING_TOKEN as string); const trpc = gettRPC(process.env.CLI_FISHING_TOKEN as string);
@ -15,7 +16,7 @@ const b = new EventEmitter();
const rl = createInterface({ const rl = createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout
}); } as any);
const user = { const user = {
_id: "stdin", _id: "stdin",
@ -26,6 +27,12 @@ const user = {
(globalThis as unknown as any).rl = rl; (globalThis as unknown as any).rl = rl;
rl.setPrompt("> "); rl.setPrompt("> ");
rl.prompt(); rl.prompt();
setPrompt();
function setPrompt() {
const color = new CosmicColor(user.color);
rl.setPrompt(`\x1b[38;2;${color.r};${color.g};${color.b}m> `);
}
rl.on("line", async line => { rl.on("line", async line => {
if (line == "stop" || line == "exit") process.exit(); if (line == "stop" || line == "exit") process.exit();
@ -94,6 +101,7 @@ setInterval(async () => {
b.on("color", msg => { b.on("color", msg => {
if (typeof msg.color !== "string" || typeof msg.id !== "string") return; if (typeof msg.color !== "string" || typeof msg.id !== "string") return;
user.color = msg.color; user.color = msg.color;
setPrompt();
}); });
b.on("sendchat", msg => { b.on("sendchat", msg => {

View File

@ -1,22 +1,32 @@
export function getHHMMSS() { export function getTime(t = Date.now(), twelveHour = true) {
const now = Date.now(); const now = t;
const s = now / 1000; const s = now / 1000;
const m = s / 60; const m = s / 60;
const h = m / 60; const h = m / 60;
const hh = Math.floor(h % 12) const hours = Math.floor(h % (twelveHour ? 12 : 24))
.toString() .toString()
.padStart(2, "0"); .padStart(2, "0");
const mm = Math.floor(m % 60) const minutes = Math.floor(m % 60)
.toString() .toString()
.padStart(2, "0"); .padStart(2, "0");
const ss = Math.floor(s % 60) const seconds = Math.floor(s % 60)
.toString() .toString()
.padStart(2, "0"); .padStart(2, "0");
const ms = Math.floor(now % 1000) const milliseconds = Math.floor(now % 1000)
.toString() .toString()
.padStart(3, "0"); .padStart(3, "0");
return `${hh}:${mm}:${ss}.${ms}`; return {
hours,
minutes,
seconds,
milliseconds
};
}
export function getHHMMSS(t = Date.now(), twelveHour = true) {
const { hours, minutes, seconds, milliseconds } = getTime(t, twelveHour);
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
} }

7
src/util/types.d.ts vendored
View File

@ -21,7 +21,7 @@ interface IContextProps {
type TCommandCallback<User> = (props: IContextProps) => Promise<string | void>; type TCommandCallback<User> = (props: IContextProps) => Promise<string | void>;
interface CountComponent { interface ICountComponent {
count: number; count: number;
} }
@ -54,7 +54,7 @@ interface IFish extends IObject {
} }
interface IPokemon extends IObject { interface IPokemon extends IObject {
id: number; id: string;
pokeID: number; pokeID: number;
objtype: "pokemon"; objtype: "pokemon";
emoji?: string; emoji?: string;
@ -78,6 +78,7 @@ type TPokemonSack = JsonArray & IPokemon[];
interface IInventory { interface IInventory {
id: number; id: number;
balance: number; balance: number;
location: string;
items: TInventoryItems; items: TInventoryItems;
fishSack: TFishSack; fishSack: TFishSack;
@ -90,7 +91,7 @@ interface IBack<T extends string | unknown> extends Record<string, unknown> {
m: T; m: T;
} }
interface Backs extends Record<string, IBack<unknown>> { interface IBacks extends Record<string, IBack<unknown>> {
color: { color: {
m: "color"; m: "color";
}; };