This commit is contained in:
cirroskais 2024-02-19 03:23:12 -05:00
parent a43b20180f
commit e0de38eb87
17 changed files with 281 additions and 26 deletions

View file

@ -0,0 +1,54 @@
import { InteractionResponseTypes } from "@discordeno/types"
import REST from "../lib/handlers/RESTHandler"
import { staged } from "../lib/modpack"
import { getAllMods } from "../lib/database"
function search(arr, search) {
if (!search.length) return false
var matches = arr.filter(function (str) {
const regex = new RegExp(search, "i")
return str.search(regex) > -1
})
return matches.length > 0 ? matches : false
}
export default class Autocomplete {
static name = "modify"
constructor(data) {}
async run(interaction) {
const entry = interaction.data.options[0].options[0].value
const from_modpack = interaction.data.options[0].options[1]?.value
if (!from_modpack) {
const keys = Array.from(staged.keys())
let choices = search(keys, entry)
if (!choices) choices = keys
return REST.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: {
choices: choices.map((_) => {
return { name: _, value: _ }
}),
},
})
} else {
const keys = getAllMods.all().map((_) => _.name)
let choices = search(keys, entry)
if (!choices) choices = keys
return REST.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: {
choices: choices.map((_) => {
return { name: _, value: _ }
}),
},
})
}
}
}

View file

@ -16,7 +16,7 @@ export default class Command extends BaseCommand {
if (!staged.size) return interaction.respond("No changes to the modpack have been staged.")
for (let [key, value] of staged.entries()) {
text += (value.action == "add" ? "+" : "-") + " " + key + " " + value.url
text += (value.action == "add" ? "+" : "-") + " " + key + " (" + value.url + ")\n"
}
interaction.respond("```diff\n" + text + "\n```")

View file

@ -3,6 +3,7 @@ import BaseCommand from "../lib/classes/BaseCommand"
const META_CHOICES = [
{ name: "loader", value: "loader" },
{ name: "minecraft_version", value: "minecraft_version" },
{ name: "version", value: "version" },
]
export default class Command extends BaseCommand {

View file

@ -21,6 +21,7 @@ export default class Command extends BaseCommand {
}
async run(interaction) {
const then = Date.now()
const { data } = interaction
const url = data.options.find((_) => _.name == "url").value
@ -45,7 +46,7 @@ export default class Command extends BaseCommand {
title: mod.name,
description: mod.summary,
url: mod.links.websiteUrl,
thumbnail: { url: mod.logo.url },
image: { url: mod.logo.url },
footer: { text: `Curseforge | ModId ${mod.id} | FileId ${file.id}` },
},
],

View file

@ -1,4 +1,7 @@
import BaseCommand from "../lib/classes/BaseCommand"
import { staged } from "../lib/modpack"
import * as curseforge from "../lib/curseforge"
import { getMod } from "../lib/database"
export default class Command extends BaseCommand {
static type = 1
@ -23,6 +26,12 @@ export default class Command extends BaseCommand {
name: "remove",
description: "Remove a mod",
options: [
{
type: 5,
name: "from_modpack",
description: "Toggle removal from staging or the modpack",
required: true,
},
{
type: 3,
name: "name",
@ -30,11 +39,6 @@ export default class Command extends BaseCommand {
autocomplete: true,
required: true,
},
{
type: 5,
name: "from_modpack",
description: "Toggle removal from staging or the modpack",
},
],
},
]
@ -44,6 +48,59 @@ export default class Command extends BaseCommand {
}
async run(interaction) {
console.log("modify.js")
const subcommand = interaction.data.options[0].name
const subcommandOptions = interaction.data.options[0].options
if (subcommand == "add") {
const url = subcommandOptions.find((_) => _.name == "url").value
const parsedUrl = new URL(url)
if (parsedUrl.host.includes("curseforge.com")) {
const mod = await curseforge.getMod(url)
if (!mod) return interaction.respond("Mod not found.")
const file = await curseforge.getModFiles(mod.id)
if (staged.get(mod.name)) return interaction.respond(`**${mod.name}** is already in the staged mods.`, { isPrivate: true })
if (getMod.get(mod.name)) return interaction.respond(`**${mod.name}** is already in the modpack.`, { isPrivate: true })
staged.set(mod.name, {
modId: mod.id,
fileId: file.id,
provider: "curseforge",
url: mod.links.websiteUrl,
action: "add",
})
interaction.respond(`Added **${mod.name}** to the staged mods.`)
} else if (parsedUrl.host.includes("modrinth.com")) {
console.log("Modrinth mod detected")
}
} else if (subcommand == "remove") {
const name = subcommandOptions.find((_) => _.name == "name").value
const from_modpack = subcommandOptions.find((_) => _.name == "from_modpack")?.value
if (!from_modpack) {
const mod = staged.get(name)
if (!mod) return interaction.respond("Mod not found.")
staged.delete(name)
interaction.respond(`Removed **${name}** from the staged mods.`)
} else {
const mod = getMod.get(name)
if (!mod) return interaction.respond("Mod not found.")
const modData = await curseforge.getModFromId(mod.modId)
staged.set(mod.name, {
modId: mod.modId,
url: modData.data.links.websiteUrl,
provider: "curseforge",
action: "remove",
})
interaction.respond(`Staged the removal of **${name}**.`)
}
}
}
}

View file

@ -1,4 +1,6 @@
import BaseCommand from "../lib/classes/BaseCommand"
import { addMod, getAllMods, getAttribute, removeMod, updateAttribute, updateChangelog } from "../lib/database"
import { staged } from "../lib/modpack"
export default class Command extends BaseCommand {
static type = 1
@ -10,6 +12,22 @@ export default class Command extends BaseCommand {
}
async run(interaction) {
console.log("publish.js")
if (!staged.size) return interaction.respond("There is nothing to publish.")
const currentVersion = Number(getAttribute.get("version").value)
const nextVersion = currentVersion + 1
let changelog = []
for (let [key, value] of staged.entries()) {
if (value.action == "remove") removeMod.run(value.modId)
if (value.action == "add") addMod.run(key, Number(value.modId), Number(value.fileId))
updateChangelog.run(key, nextVersion, value.action == "remove" ? 0 : 1)
staged.delete(key)
}
updateAttribute.run("version", nextVersion)
interaction.respond(`Version **${nextVersion}** was published.`)
}
}

View file

@ -2,7 +2,7 @@ import { InteractionResponseTypes } from "@discordeno/types"
import * as curseforge from "../lib/curseforge"
import REST from "../lib/handlers/RESTHandler"
import { staged } from "../lib/modpack"
import { cache } from "../lib/cache"
import { getMod } from "../lib/database"
export default class Command {
static name = "addMod"
@ -18,7 +18,8 @@ export default class Command {
if (provider == "curseforge") {
const mod = await curseforge.getModFromId(modId)
if (staged.get(mod.data.name)) return interaction.respond(`**${mod.data.name}** is already in the modpack.`, { isPrivate: true })
if (staged.get(mod.data.name)) return interaction.respond(`**${mod.data.name}** is already in the staged mods.`, { isPrivate: true })
if (getMod.get(mod.data.name)) return interaction.respond(`**${mod.data.name}** is already in the modpack.`, { isPrivate: true })
staged.set(mod.data.name, {
modId,
@ -28,7 +29,7 @@ export default class Command {
action: "add",
})
interaction.respond(`Added **${mod.data.name}** to the modpack.`)
interaction.respond(`Added **${mod.data.name}** to the staged mods.`)
} else {
interaction.respond("Not implemented for provider " + provider)
}

View file

@ -1,6 +1,7 @@
import { logger } from "@discordeno/utils"
import CommandHandler from "../lib/handlers/CommandHandler"
import AutocompleteHandler from "../lib/handlers/AutocompleteHandler"
import ComponentHandler from "../lib/handlers/ComponentHandler"
const Commands = new CommandHandler()
@ -13,7 +14,13 @@ await Components.load((comp) => {
console.log("Loaded component response " + comp.name)
})
const Autocomplete = new AutocompleteHandler()
await Autocomplete.load((comp) => {
console.log("Loaded autocomplete response " + comp.name)
})
export default async function (client, interaction) {
if (interaction.type == 2) Commands.check(client, interaction)
if (interaction.type == 3) Components.check(client, interaction)
if (interaction.type == 2) return Commands.check(client, interaction)
if (interaction.type == 3) return Components.check(client, interaction)
if (interaction.type == 4) return Autocomplete.check(client, interaction)
}

View file

@ -1,2 +1,3 @@
//lol
export const cache = new Map()
export let cfModCache = new Map()
export let cfModIdCache = new Map()
export let cfModFilesCache = new Map()

View file

@ -1,3 +1,5 @@
import { cfModCache, cfModFilesCache, cfModIdCache } from "./cache"
const BASE_URL = "https://api.curseforge.com"
const SEARCH_MODS = "/v1/mods/search"
const GET_FILES = "/v1/mods/{modId}/files"
@ -9,6 +11,8 @@ export async function getMod(url, gameVersion = "1.18.2", modLoaderType = 1) {
url = url.split("/")
const slug = url[url.length - 1]
if (cfModCache.get(slug)) return cfModCache.get(slug)
const query = new URLSearchParams()
query.append("gameId", GAME_ID)
query.append("slug", slug)
@ -22,20 +26,29 @@ export async function getMod(url, gameVersion = "1.18.2", modLoaderType = 1) {
},
}).catch((e) => console.log(e))
return (await response.json()).data[0]
const data = (await response.json()).data[0]
cfModCache.set(slug, data)
return data
}
export async function getModFromId(modId) {
if (cfModIdCache.get(modId)) return cfModIdCache.get(modId)
const response = await fetch(BASE_URL + GET_MOD.replace("{modId}", modId), {
headers: {
"x-api-key": import.meta.env.CURSEFORGE_API,
},
}).catch((e) => console.log(e))
return await response.json()
const data = await response.json()
cfModIdCache.set(modId, data)
return data
}
export async function getModFiles(modId, gameVersion = "1.18.2", modLoaderType = 1) {
if (cfModFilesCache.get(modId)) return cfModFilesCache.get(modId)
const query = new URLSearchParams()
if (gameVersion) query.append("gameVersion", gameVersion)
if (modLoaderType) query.append("modLoaderType", modLoaderType)
@ -46,7 +59,9 @@ export async function getModFiles(modId, gameVersion = "1.18.2", modLoaderType =
},
}).catch((e) => console.log(e))
return (await response.json()).data[0]
const data = (await response.json()).data[0]
cfModFilesCache.set(modId, data)
return data
}
export async function downloadMod(id, fileId) {}

View file

@ -1,9 +1,23 @@
import { Database } from "bun:sqlite"
const database = new Database("data/database.sqlite")
database.run(`CREATE TABLE IF NOT EXISTS attributes (key INTEGER PRIMARY KEY, value INTEGER NOT NULL);`)
database.run(`CREATE TABLE IF NOT EXISTS attributes (key STRING PRIMARY KEY, value STRING NOT NULL);`)
database.run(`CREATE TABLE IF NOT EXISTS mods (name STRING PRIMARY KEY, modId INTEGER NOT NULL, fileId INTEGER NOT NULL);`)
database.run(`CREATE TABLE IF NOT EXISTS changelog (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING NOT NULL, version INTEGER NOT NULL, action BOOLEAN NOT NULL);`)
database.run(`CREATE TABLE IF NOT EXISTS mods (url INTEGER PRIMARY KEY);`)
database.run(`INSERT OR IGNORE INTO attributes (key, value) VALUES ('version', 0);`)
export const addMod = database.prepare(`INSERT INTO mods (name, modId, fileId) VALUES (?1, ?2, ?3);`)
export const removeMod = database.prepare(`DELETE FROM mods WHERE modId = ?1;`)
export const getAllMods = database.prepare(`SELECT * FROM mods;`)
export const getMod = database.prepare(`SELECT * FROM mods WHERE name = ?1;`)
export const addAttribute = database.prepare(`INSERT INTO attributes (key, value) VALUES (?1, ?2);`)
export const updateAttribute = database.prepare(`UPDATE attributes SET value = ?2 WHERE key = ?1;`)
export const getAttribute = database.prepare(`SELECT value FROM attributes WHERE key = ?1;`)
export const updateChangelog = database.prepare(`INSERT INTO changelog (name, version, action) VALUES (?1, ?2, ?3);`)
export const getChangelog = database.prepare(`SELECT * FROM changelog WHERE version = ?1;`)
process.on("exit", () => {
database.close()

View file

@ -0,0 +1,40 @@
import { readdir } from "node:fs/promises"
export default class AutocompleteHandler {
constructor() {
this.cache = new Map()
}
async load(postLoad = () => {}) {
const dir = await readdir("src/autocomplete")
for (let file of dir) {
const cmd = (await this.loadCommand(file)).default
this.cache.set(cmd.name, cmd)
postLoad(cmd)
}
return this.cache
}
async loadCommand(path) {
return await import(process.cwd() + "/src/autocomplete/" + path)
}
reloadCommand(name) {}
unloadCommand(name) {}
check(client, data) {
if (data.type !== 4) return false
this.run(client, data)
}
run(client, interaction) {
const cmd = this.cache.get(interaction.data.name)
if (!cmd) return false
const Command = new cmd(client)
return Command.run(interaction)
}
}

View file

@ -1 +1,9 @@
export default async function route(req) {}
import { getAllMods } from "../lib/database"
export default async function route(req) {
const mods = getAllMods.all()
return new Response(JSON.stringify(mods), {
headers: { "content-type": "application/json" },
})
}

View file

@ -1 +0,0 @@
export default async function route(req) {}

View file

@ -1 +1,30 @@
export default async function route(req) {}
import { getAttribute, getChangelog } from "../lib/database"
export default async function route(req) {
const from = Number(new URL(req.url).searchParams.get("from"))
if (isNaN(from))
return new Response("400: Missing 'from' in query", {
status: 400,
})
const current = Number(getAttribute.get("version").value)
if (from > current)
return new Response("400: Requested version is higher than current", {
status: 400,
})
if (from == current)
return new Response(null, {
status: 204,
})
let diff = []
const currentChangelog = getChangelog.all(getAttribute.get("version").value)
const thenChangelog = getChangelog.all(from)
return new Response(JSON.stringify({ currentChangelog, thenChangelog }), {
headers: { "content-type": "application/json" },
})
}

View file

@ -1 +1,7 @@
export default async function route(req) {}
import { getAttribute } from "../lib/database"
export default async function route(req) {
return new Response(JSON.stringify({ version: getAttribute.get("version").value }), {
headers: { "content-type": "application/json" },
})
}

View file

@ -1,7 +1,11 @@
import getChanges from "./routes/getUpdates"
import getAllMods from "./routes/getAllMods"
import index from "./routes/index"
const ROUTES = {
"/": index,
"/getallmods": getAllMods,
"/changes": getChanges,
}
export default async function server() {
@ -9,7 +13,7 @@ export default async function server() {
fetch(req) {
const url = new URL(req.url)
const route = ROUTES[url.pathname]
if (route) return route()
if (route) return route(req)
throw new Error(404)
},
error(error) {