diff --git a/config/permissions.yml b/config/permissions.yml new file mode 100644 index 0000000..21de734 --- /dev/null +++ b/config/permissions.yml @@ -0,0 +1,9 @@ +- id: default + permissions: + - command.general.* + - command.fishing.* + - command.inventory.* + - command.util.* +- id: owner + permissions: + - "*" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e205872..ad1a8f3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -46,3 +46,8 @@ model KeyValueStore { id Int @id @default(0) json Json @default("{}") } + +model UserPermission { + userId String @id @unique + groupId String +} diff --git a/src/api/cli/commands/setgroup.ts b/src/api/cli/commands/setgroup.ts new file mode 100644 index 0000000..f663375 --- /dev/null +++ b/src/api/cli/commands/setgroup.ts @@ -0,0 +1,21 @@ +import { getAllTokens } from "@server/data/token"; +import { ReadlineCommand } from "../ReadlineCommand"; +import { setUserGroup } from "@server/data/permissions"; + +export const setgroup = new ReadlineCommand( + "setgroup", + ["setgroup"], + "Set a user's permission group", + "setgroup ", + async line => { + const args = line.split(" "); + const userID = args[1]; + const groupID = args[2]; + + if (!userID) return "Please provide a user ID."; + if (!groupID) return "Please provide a group ID."; + + await setUserGroup(userID, groupID); + return `User's group is now ${groupID}.`; + } +); diff --git a/src/api/commands/groups/fishing/pick.ts b/src/api/commands/groups/fishing/pick.ts new file mode 100644 index 0000000..d925920 --- /dev/null +++ b/src/api/commands/groups/fishing/pick.ts @@ -0,0 +1,318 @@ +import Command from "@server/commands/Command"; +import { getInventory, updateInventory } from "@server/data/inventory"; +import { genFruitAndRemove, getFruitCount, hasFruit } from "@server/fish/tree"; +import { addItem } from "@server/items"; + +export const pick = new Command( + "pick", + ["pick", "pock"], + "Pick a fruit off the Kekklefruit Tree.", + "pick", + "command.fishing.pick", + async ({ id, command, args, prefix, part, user, isDM }) => { + const inventory = await getInventory(user.inventoryId); + if (!inventory) return; + + if (!(await hasFruit())) + return crazy[Math.floor(Math.random() * crazy.length)]; + + const fruit = await genFruitAndRemove(); + addItem(inventory.items as unknown as IObject[], fruit); + await updateInventory(inventory); + return `Our friend ${part.name} picked 1 fruit from the kekklefruit tree and placed it into his/her inventory.`; + } +); + +const crazy = [ + "The tree is devoid of fruit.", + "The tree is without fruit.", + "The tree is barren.", + "The tree is missing all its fruit.", + "The tree is not with fruit.", + "The tree is without fruit.", + "The tree is not showing any fruit.", + "The tree is not bearing fruit.", + "The tree has not borne fruit.", + "The tree is not showing fruit.", + "The tree is not carrying fruit.", + "The tree is not holding fruit.", + "The tree is at 0 fruit.", + "The tree has no fruit.", + "The tree doesn't have any fruit to give.", + "The tree doesn't have any fruit to take.", + "The tree doesn't have any fruit left to plunder...", + "The tree has not grown any new fruit.", + "The tree can't give any more fruit right now.", + "The fruit have all been taken.", + "The fruit have all been picked.", + "You don't see any fruit on the tree.", + "Your hand is without fruit. After reaching to pick one", + "No fruit because there aren't any on the tree.", + "No kekklefruit was upon the tree.", + "The tree has long slender limbs, barren of fruit.", + "The tree's limbs are not currently baring any fruit.", + "This tree doesn't have fruit.", + "Fruit are not a thing currently on the tree.", + "Could not get fruit.", + "Try again, please.", + "(no fruit picked)", + "It just doesn't have any fruit.", + "There aren't any fruit.", + "Can't get fruit, there's no fruit.", + "The tree's not growing!!!!!!!", + "Give the tree some time to grow fruit.", + "The tree will grow fruit given time.", + "The tree will have fruit again.", + "The tree's just sitting there. Fruitless.", + "It'll grow fruit, give it a second.", + "Keep trying, but wait until the tree has fruit.", + "Wait until the tree has fruit.", + "Pick again in a bit because the tree doesn't have any fruit right now.", + "There aren't any fruit on the kekklefruit tree", + "You pore over each branch meticulously looking for fruit, but are still coming back empty.", + "You scour every branch of the tree for fruit, but still came back empty-handed.", + "You try caressing the tree's body. It didn't work.", + "You try tugging on one of the branches. It doesn't work.", + "You started picking the fruit when you heard a sound or something that distracted you and made you forget what you were doing. Then, you remember: you tried to pick a fruit. You take a deep breath and decide to try again", + "You could have sworn you were wrapping your hand around a sweet kekklefruit, but it seemingly disappeared from reality right as you grasped it??", + "No fruit.", + "Trying again, there were no fruit to pick.", + "There were no fruit to pick.", + "There was no fruit for you to pick.", + "There isn't anything that looks like a fruit growing on the tree, yet...", + "The fruit just isn't edible yet.", + "It's not ready, keep trying though.", + "It's not ready...!", + "It's not done.", + "Wait, give it time to grow fruit.", + "Just wait for the fruit to grow.", + "Wait for the fruit to grow. But don't wait until someone else grabs it first.", + "You have to give the precious kekklefruits time to grow.", + "Hold on, they're growing.", + "Hold on.", + "Watch the kekklefruit to make sure they have grown before picking them from the tree.", + "Don't pick the kekklefruit until they're grown.", + "The kekklefruit are still maturing.", + "There isn't a pickable kekklefruit.", + "You don't see any.", + "I don't see any.", + "It's like every time the tree grows fruit somebody is stealing it.", + "Every time the tree grows fruit, somebody picks it.", + "There's no fruit, so wait.", + "Keep trying to get fruit.", + "The fruit will be fine... when it grows.", + "The fruit will do fine. Then, pick it.", + "The fruit looks like you could almost pick it!", + "Picking is not available right now.", + "Please try again later.", + "No fruit.", + "Look here. Look there. No fruit anywhere.", + "The fruit just isn't there to pick.", + "You can't pick the fruit because it's not ready to be picked.", + "Don't pick the fruit until it finishes growing into a pickable fruit.", + "Let the fruit grow, first.", + "The tree is out of fruit.", + "The tree's fruit count remains 0.", + "Tree fruit unavailable.", + "You try, but there's no fruit.", + "The tree ran out of fruit.", + "No pickable fruit.", + "People took the tree's fruit.", + "The tree was picked over entirely.", + "The tree just didn't have any more fruit to give.", + "The tree asked you to try again, please.", + "The tree branches looked sinister with no fruit on them at all.", + "Without its fruit, the tree looks kinda scary.", + "The tree doesn't have fruit anymore.", + "The tree doesn't have fruit anymore. It looks weird that way.", + "The tree's long slender branches reached high into the sky, looking nude without their fruit.", + "Robbed of its precious fruit, the tree loomed despondently.", + 'The tree doesn\'t "have" fruit.', + "After much consideration, you decide to maybe sayer a prayer for the tree.", + "The action you have taken upon the tree was fruitless.", + "No fruit, just now, not on the tree, here.", + "You didn't get any fruit.", + "The tree's fruit supply is depleted.", + "This tree has a strange animosity.", + "They took it all.", + "There's no more fruit.", + "Don't have any fruit.", + "You just have to wait for kekklefruit.", + "Wait for fruit.", + "Wait for fruit growth.", + "Wait for the fruit growth.", + "Wait for fruit to grow on the tree.", + "Those tree fruit are just hard to come by right now.", + "I haven't seen a fruit", + "It didn't produce fruit yet.", + "You're still waiting for it to produce fruit.", + "You're still waiting for fruit to grow.", + "The tree is bone dry! Sans fruit!", + "God, you'd do anything for a fruit. But not yet.", + "Just be patient.", + "Be patient.", + "Wait patiently for fruit.", + "Your fruit will grow, just wait.", + "Waiting for your fruit to grow.", + "Pick the next fruit that grows.", + "Pick a fruit after it grows.", + "Get a fruit from the tree after they grow.", + "Pick again after the tree has had time to grow fruit.", + "Not yet, it's hasn't grown fruit yet.", + "Wait a second, no fruit yet.", + "You can has fruit after it grows.", + "Try again repeatedly to see if you get a fruit or not.", + "Try again, it grows fruit periodically.", + "Wait", + "No fruit just yet", + "No fruit yet", + "Noooot yet", + "Just a little longer.", + "Wait between each pick for fruit to grow.", + "After a wait, fruit will grow on the tree.", + "The tree's gonna grow plenty of fruit, just give it time.", + "Without its fruit, this tree is looking slightly eerie.", + "What a funny-looking tree without its fruit!", + "You notice the way the tree looks without fruit.", + "You notice the tree looks kinda odd with no fruit like that.", + "You don't like looking at the tree when it doesn't have fruit.", + "You express your desire for the tree to grow fruit.", + "You're waiting for the fruit to grow so you can pick it.", + "Ugh, no fruit..", + "Keep trying to get fruit.", + "The fruit gave under the forces... I guess it wasn't ready yet.", + "The fruit's branches hadn't decided to tree yet.", + "The fruit wasn't available.", + "It's almost time for a fruit to be pickable.", + "Should be a fruit pickable soon.", + "It'll grow fruit for you to pick in a minute.", + "It'll grow in a minute.", + "It'll grow.", + "It'll grow fruit.", + "The fruit will grow on the tree's BRANCHES.", + "You don't spy any fruit on the tree's branches.", + "The tree's branches can be seen in detail without the fruit interrupting our view.", + "You make sure, and there's no fruit on the tree.", + "You search the tree for fruit, and are 100% sure there are none.", + "You're 100% sure there aren't any pickable fruit yet.", + "You try, but don't find any fruit.", + "You look, but don't find any fruit.", + "Can't see any FRUIT.", + "Couldn't /pick", + "It's just that there aren't any fruit on the tree.", + "These things take time.", + "These things can sometimes take time.", + "You can't rush these things.", + "You practice picking the fruit (there aren't any on the tree)", + "It doesn't look like there are any fruit on the tree.", + "0 kinds of fruit are growing on this tree", + "You feel good about the possibility of fruit growing on the tree eventually.", + "You whisper for the tree to grow nice fruits.", + "This is exciting! It'll grow fruit that you can eat.", + "Alas, the tree wasn't currently displaying any fruit.", + "Any fruit on the tree? No...", + "No fruit? Okay...", + "A quick scan shows no fruits on the tree that are ready for picking.", + "You check and don't see any fruit.", + "You give the tree a once-over to see if any fruit area ready. Not yet, but you are resolute...", + "You check on the tree. No fruit, back to whatever it was you were doing.", + "If this tree doesn't grow fruit soon you might start to get crazy.", + "Actually, what if the tree doesn't grow any more fruit?", + "What if the fruit never grows again?", + "Ok, there's no fruit.", + "You consider again what might happen if the fruit stopped growing.", + "There is no fruit, so you just ponder about the tree.", + "There's no fruit, so you just consider it for a moment.", + "There's no fruit, so you think about the tree situation for another moment and then move on.", + "There are no fruits, so you decided to talk about something else.", + "Missed!", + "Didn't chance upon a fruit.", + "Didn't find the fruit.", + "No fruit found.", + "It's gonna be good fruit.", + "The fruit from the tree will never change.", + "The fruit from this tree will always grow, as long as the tree stands, at a pretty steady rate.", + "You survey the tree for fruit, coming back empty-handed.", + "It's not like the tree is on strike from producing fruit.", + "The valuable fruit are not present.", + "The revered fruit have been lost.", + "You study the tree's fruitless branches.", + "Good view of the branches with no fruit on them.", + "Patiently and rapidly retry your command.", + "You use a phone app to make sure the tree doesn't have any pickable fruit.", + "You scan each fruit, finding no candidates for picking.", + "The fruit of the tree are too young and supple to pick.", + "You can't reach that one fruit up there.", + "Oh, there's one. But you can't reach it.", + "You trying to pick fruit that isn't there.", + "Where do you see fruit?", + "Looks like the fruit aren't out today.", + "You wonder what the fruit are doing.", + "You wonder when the tree will bear fruit.", + "You wonder when a fruit will be ready.", + "You wonder if a fruit will grow.", + "You think about how many fruits this tree must have produced with nobody even counting it or anything.", + "You wonder how many fruit this tree has grown in its lifetime.", + "It's not that time, yet.", + "It's not time.", + "Not... yet.", + "The auto-analysis didn't show any completed fruit.", + "The fruit aren't complete.", + "Waiting for fruit growth completion.", + "Please wait for the fruit to be ready.", + "Don't rush it.", + "Slow down, there aren't any fruit to pick yet.", + "You check the fruit indicator under your favorite kekklefruit tree. It reads: 0.", + "Nope, don't see any.", + "Is something taking all the fruit?", + "I guess somebody else picked the fruit first.", + "Somebody else got to it first.", + "This", + "If you focus, the fruit grows faster.", + "You meditate to make the fruit grow faster.", + "What you are doing doesn't make the fruit grow.", + "Don't be too greedy.", + "Fruit pick intercepted.", + "Intercepted, try again.", + "Denied. Try again for success.", + "False success message, no fruit actually picked", + "I swear it'll grow fruit eventually lol", + "You don't know how long it'll take before fruit grows on the tree.", + "You don't know how long before the fruit will grow on the tree.", + "Nobody knows how long it takes for fruit to grow on the tree.", + "The tree says 'no'", + "No fruit, but that's okay.", + "Don't worry about it.", + "No fruit but it's quite alright.", + "No fruit right now.", + "Not a good time to pick fruit.", + "It's probably not a good idea", + "Ha ha don't worry!", + "Lol don't sweat it", + "It's alright! It's just a temporary lack of fruit!", + "Seems like famine again", + "What's wrong with the tree?", + "Is the tree okay?", + "What's this tree for...?", + "Is something wrong with the tree?", + "Try singing the tree a song.", + "The tree doesn't look like it's up to it righ tnow.", + "The tree doesn't look so good.", + "The tree doesn't feel so good.", + "The tree doesn't look like it feels so good.", + "The tree isn't ready right now!", + "Back off and give the tree some time!!", + "Hands off until the tree grows fruit.", + "Patience.", + "Impatience.", + "no", + "Fruit not available", + "There are no fruits there.", + "No fruits upon the tree!", + "That didn't work.", + "Nope, no fruit.", + "You thought you spied a fruit, but were unable to procure any.", + "You climb all over that tree and don't find a single pickable", + "You wouldn't steal a fruit from a tree with no fruit.", + "Are you sure there aren't any fruit just lying around on the ground that you can /take?" +]; diff --git a/src/api/commands/groups/fishing/tree.ts b/src/api/commands/groups/fishing/tree.ts new file mode 100644 index 0000000..972e7b8 --- /dev/null +++ b/src/api/commands/groups/fishing/tree.ts @@ -0,0 +1,15 @@ +import Command from "@server/commands/Command"; +import { getFruitCount } from "@server/fish/tree"; + +export const tree = new Command( + "tree", + ["tree", "troo", "truu", "traa"], + "Check how many fruit are on the Kekklefruit Tree.", + "tree", + "command.fishing.tree", + async ({ id, command, args, prefix, part, user, isDM }) => { + const num = await getFruitCount(); + + return `Friend ${part.name}: ${num}`; + } +); diff --git a/src/api/commands/groups/index.ts b/src/api/commands/groups/index.ts index 5491d16..151ac58 100644 --- a/src/api/commands/groups/index.ts +++ b/src/api/commands/groups/index.ts @@ -19,6 +19,8 @@ import { autofish } from "./util/autofish"; import { pokedex } from "./util/pokedex"; import { myid } from "./general/myid"; import { yeet } from "./inventory/yeet"; +import { tree } from "./fishing/tree"; +import { pick } from "./fishing/pick"; interface ICommandGroup { id: string; @@ -39,7 +41,7 @@ commandGroups.push(generalGroup); const fishingGroup: ICommandGroup = { id: "fishing", displayName: "Fishing", - commands: [fish, reel, location, go, nearby, look] + commands: [fish, reel, location, go, nearby, look, tree, pick] }; commandGroups.push(fishingGroup); diff --git a/src/api/commands/groups/util/permission.ts b/src/api/commands/groups/util/permission.ts new file mode 100644 index 0000000..66fec39 --- /dev/null +++ b/src/api/commands/groups/util/permission.ts @@ -0,0 +1,13 @@ +import Command from "@server/commands/Command"; + +export const group = new Command( + "group", + ["group"], + "Get user group", + "group", + "command.util.group", + async props => { + return JSON.stringify(props); + }, + false +); diff --git a/src/api/commands/handler.ts b/src/api/commands/handler.ts index 8dc7c3c..2381bb6 100644 --- a/src/api/commands/handler.ts +++ b/src/api/commands/handler.ts @@ -3,6 +3,8 @@ import type Command from "./Command"; import { commandGroups } from "./groups"; import { createUser, getUser, updateUser } from "@server/data/user"; import { createInventory, getInventory } from "@server/data/inventory"; +import { getUserGroup } from "@server/data/permissions"; +import { groupHasPermission } from "@server/permissions/groups"; export const logger = new Logger("Command Handler"); @@ -52,7 +54,10 @@ export async function handleCommand( let inventory = await getInventory(user.inventoryId); if (!inventory) inventory = await createInventory({ id: user.inventoryId }); - // TODO Check user's (or their groups') permissions against command permission node + const group = await getUserGroup(user.id); + if (!group) return; + if (!groupHasPermission(group.groupId, foundCommand.permissionNode)) + return { response: `No permission.` }; try { const response = await foundCommand.callback({ diff --git a/src/api/data/permissions.ts b/src/api/data/permissions.ts new file mode 100644 index 0000000..764d70b --- /dev/null +++ b/src/api/data/permissions.ts @@ -0,0 +1,44 @@ +import type { User } from "@prisma/client"; +import prisma from "./prisma"; + +export async function setUserGroup(userID: User["id"], groupID: string) { + const existing = await getUserGroup(userID); + if (!existing) createUserGroup(userID, "default"); + return await prisma.userPermission.update({ + where: { + userId: userID, + groupId: groupID + }, + data: { + groupId: groupID, + userId: userID + } + }); +} + +export async function getUserGroup(userID: User["id"]) { + const existing = await prisma.userPermission.findUnique({ + where: { + userId: userID + } + }); + + if (existing) return existing; + + await createUserGroup(userID, "default"); + + return await prisma.userPermission.findUnique({ + where: { + userId: userID + } + }); +} + +export async function createUserGroup(userID: User["id"], groupID: string) { + return await prisma.userPermission.create({ + data: { + userId: userID, + groupId: groupID + } + }); +} diff --git a/src/api/fish/locations.ts b/src/api/fish/locations.ts index aeaaec7..cbf731a 100644 --- a/src/api/fish/locations.ts +++ b/src/api/fish/locations.ts @@ -35,6 +35,14 @@ export const locations = loadConfig("config/locations.yml", [ nearby: ["pond", "lake", "river"], hasSand: true, objects: [] + }, + + { + id: "forest", + name: "Forest", + nearby: ["pond", "lake", "beach"], + hasSand: false, + objects: [] } ]); diff --git a/src/api/fish/tree.ts b/src/api/fish/tree.ts new file mode 100644 index 0000000..6c15630 --- /dev/null +++ b/src/api/fish/tree.ts @@ -0,0 +1,51 @@ +import { createKeyValueStore, kvGet, kvSet } from "@server/data/keyValueStore"; +import { addTickEvent } from "@util/tick"; + +const key = "tree"; + +export async function getFruitCount() { + return await kvGet(key); +} + +export async function setFruitCount(num: number) { + await kvSet(key, num); +} + +export async function treeTick() { + const r = Math.random(); + + if (r < 0.00001) { + await growFruit(5); + } else if (r < 0.0001) { + await growFruit(1); + } +} + +export async function initTree() { + const num = await kvGet(key); + if (typeof num !== "number") kvSet(key, 0); + + addTickEvent(treeTick); +} + +export async function genFruitAndRemove(): Promise { + await growFruit(-1); + + return { + id: "kekklefruit", + name: "Kekklefruit", + objtype: "item", + emoji: "🍍" + }; +} + +export async function hasFruit() { + const num = await getFruitCount(); + if (num <= 0) return false; + return true; +} + +export async function growFruit(num: number) { + const old = await getFruitCount(); + await setFruitCount(old + num); +} diff --git a/src/api/index.ts b/src/api/index.ts index b9e69b1..d6b643c 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,6 +2,8 @@ import "./api/server"; import "./cli/readline"; import { startFisherTick } from "./fish/fishers"; import { startObjectTimers } from "./fish/locations"; +import { initTree } from "./fish/tree"; startObjectTimers(); await startFisherTick(); +await initTree(); diff --git a/src/api/permissions/groups.ts b/src/api/permissions/groups.ts new file mode 100644 index 0000000..c442c4a --- /dev/null +++ b/src/api/permissions/groups.ts @@ -0,0 +1,73 @@ +import { Logger } from "@util/Logger"; +import { loadConfig } from "@util/config"; + +const logger = new Logger("Permission Handler"); + +export const groups = loadConfig("config/permissions.yml", [ + { + id: "default", + permissions: [ + "command.general.*", + "command.fishing.*", + "command.inventory.*", + "command.util.*" + ] + }, + { + id: "owner", + permissions: ["*"] + } +]); + +groups.sort((a, b) => a.id.localeCompare(b.id)); + +export function findGroup( + id: string, + index: number = Math.floor(groups.length / 2), + rec: number = 0 +) { + // let num = groups[index].id.localeCompare(id); + // if (groups[index].id == id) return groups[index]; + // rec++; + + // if (num > 1) { + // findGroup(id, Math.floor(index / 2), rec); + // } else if (num < 1) { + // findGroup(id, Math.floor(index / 2) + index, rec); + // } + + // logger.debug("Here 2"); + + return groups.find(g => g.id == id); +} + +export function groupHasPermission(groupID: IGroup["id"], handle: string) { + const group = findGroup(groupID); + if (!group) return false; + + for (const permission of group.permissions) { + if (checkPermission(handle, permission)) return true; + } +} + +export function checkPermission(p1: string, p2: string) { + const ps1 = p1.split("."); + const ps2 = p2.split("."); + + for (let i = 0; i < ps1.length; i++) { + if (ps1[i] == ps2[i]) { + if (i == ps1.length - 1 || i == ps2.length - 1) { + return true; + } else { + continue; + } + } + + if (ps1[i] == "*") return true; + if (ps2[i] == "*") return true; + + return false; + } + + return false; +} diff --git a/src/api/permissions/index.ts b/src/api/permissions/index.ts new file mode 100644 index 0000000..1535c4d --- /dev/null +++ b/src/api/permissions/index.ts @@ -0,0 +1,9 @@ +import type { User } from "@prisma/client"; +import { getUserGroup } from "@server/data/permissions"; + +export async function hasPermission(userID: User["id"], handle: string) { + const dbgroup = await getUserGroup(userID); + if (!dbgroup) return false; + dbgroup.groupId; + return false; +} diff --git a/src/cli/index.ts b/src/cli/index.ts index d6bfe8a..1094a00 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -64,7 +64,7 @@ rl.on("line", async line => { if (!command) return; if (command.response) { - logger.info(cliMd(command.response)); + logger.info(cliMd(command.response.split("\n").join("\n\n"))); } }); diff --git a/src/util/types.d.ts b/src/util/types.d.ts index c977973..e502e97 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -123,3 +123,8 @@ interface IItemBehaviorData { type TItemBehavior = Behavior; type TItemBehaviorMap = TBehaviorMap; + +interface IGroup { + id: string; + permissions: string[]; +}