Yay
This commit is contained in:
parent
a43b20180f
commit
e0de38eb87
17 changed files with 281 additions and 26 deletions
54
src/autocomplete/modify.js
Normal file
54
src/autocomplete/modify.js
Normal 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: _ }
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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```")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}` },
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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}**.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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()
|
||||
|
|
40
src/lib/handlers/AutocompleteHandler.js
Normal file
40
src/lib/handlers/AutocompleteHandler.js
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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" },
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export default async function route(req) {}
|
|
@ -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" },
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue