Compare commits
4 Commits
5cc71cfdbb
...
919e2ff7ad
Author | SHA1 | Date |
---|---|---|
Hri7566 | 919e2ff7ad | |
Hri7566 | 1a8a272c87 | |
Hri7566 | ee3629faa6 | |
Hri7566 | 0456fd9226 |
|
@ -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
|
||||||
|
|
|
@ -2,7 +2,3 @@
|
||||||
channel:
|
channel:
|
||||||
id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧"
|
id: "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧"
|
||||||
allowColorChanging: true
|
allowColorChanging: true
|
||||||
- uri: wss://mppclone.com:8443
|
|
||||||
channel:
|
|
||||||
id: "test/fishing"
|
|
||||||
allowColorChanging: true
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
16
package.json
16
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
|
@ -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>) {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue