Compare commits

..

No commits in common. "919e2ff7ad2633c1fb031ef65c71f3a00a482e92" and "5cc71cfdbbffcac6685cd67e409f1ac8e0d48d4b" have entirely different histories.

16 changed files with 71 additions and 181 deletions

View File

@ -1,5 +1,3 @@
<img src="./fish_icon.png" width="64" alt="Crucian Carp" />
# fishing-api
This is a rewrite of Brandon Lockaby's fishing bot for Multiplayer Piano.
@ -18,16 +16,14 @@ Copy the default `.env` file:
cp .env.template .env
```
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:
Edit that file to match your environment, then install packages:
```bash
bun install
bunx prisma db push
```
Run both the HTTP API and the clients for various services separately with these commands:
Run both the http server and the clients for various services separately with these commands:
```bash
bun . # Main http server

BIN
bun.lockb

Binary file not shown.

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

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

View File

@ -1,7 +1,5 @@
import Command from "@server/commands/Command";
import { logger } from "@server/commands/handler";
import { getInventory, updateInventory } from "@server/data/inventory";
import { removeItem } from "@server/items";
import { itemBehaviorMap, runBehavior } from "@server/items/behavior";
export const eat = new Command(
@ -64,9 +62,45 @@ export const eat = new Command(
if (shouldRemove) {
if (foundObject.objtype == "fish") {
removeItem(inventory.fishSack, foundObject);
i = 0;
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") {
removeItem(inventory.items, foundObject);
i = 0;
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);

View File

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

View File

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

View File

@ -23,26 +23,3 @@ export function addItem(arr: IObject[], item: IObject) {
(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;
}

View File

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

View File

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

View File

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

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

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