argv-esque argument parsing

This commit is contained in:
Cynthia Foxwell 2022-11-29 18:15:41 -07:00
parent 45fd492301
commit e2819ce5a5
7 changed files with 108 additions and 59 deletions

View file

@ -1,6 +1,58 @@
const logger = require("./logger.js"); const logger = require("./logger.js");
const {pastelize, getTopColor} = require("./utils.js"); const {pastelize, getTopColor} = require("./utils.js");
function tobool(val) {
if (isNaN(val)) {
if (val.toString().toLowerCase() === "true") {
return true;
} else if (val.toString().toLowerCase() === "false") {
return false;
} else {
return val;
}
} else {
return Number(val);
}
}
function removeStartHyphens(val) {
return val.replace(/^-+/g, "");
}
// taken from ethanent/gar
// modified to make - arguments only be bools unless =
function parseAsArgv(argv) {
const optional = {};
const args = [];
for (const arg of argv) {
const equalsIndex = arg.indexOf("=");
const argName =
equalsIndex === -1
? removeStartHyphens(arg)
: removeStartHyphens(arg.slice(0, equalsIndex));
if (equalsIndex !== -1) {
props[argName] = convertIfApplicable(arg.slice(equalsIndex + 1));
} else if (arg.charAt(0) === "-") {
if (arg.charAt(1) === "-") {
props[argName] = true;
} else {
for (let b = 0; b < argName.length; b++) {
props[argName.charAt(b)] = true;
}
}
} else {
args.push(convertIfApplicable(argName));
}
}
return {
optional,
args,
};
}
function parseArguments(str) { function parseArguments(str) {
return str.match(/\\?.|^$/g).reduce( return str.match(/\\?.|^$/g).reduce(
(p, c) => { (p, c) => {
@ -18,7 +70,7 @@ function parseArguments(str) {
).a; ).a;
} }
async function runCommand(msg, cmd, line, args) { async function runCommand(msg, cmd, line) {
let cmdObj = hf.commands.get(cmd); let cmdObj = hf.commands.get(cmd);
if (!cmdObj) { if (!cmdObj) {
for (const c of hf.commands.values()) { for (const c of hf.commands.values()) {
@ -43,7 +95,10 @@ async function runCommand(msg, cmd, line, args) {
} }
try { try {
const ret = cmdObj.callback(msg, line, ...args); const args = parseArguments(line);
const argv = parseAsArgv(args);
const ret = cmdObj.callback(msg, line, argv.args, argv.optional);
if (ret instanceof Promise) { if (ret instanceof Promise) {
return await ret; return await ret;
} else { } else {
@ -80,12 +135,10 @@ async function CommandDispatcher(msg) {
cmd = cmd.toLowerCase(); cmd = cmd.toLowerCase();
line = line.join(" "); line = line.join(" ");
const args = parseArguments(line);
try { try {
msg.hasRan = true; const response = await runCommand(msg, cmd, line);
const response = await runCommand(msg, cmd, line, args);
if (response != null) { if (response != null) {
msg.hasRan = true;
if (response.file) { if (response.file) {
const newFile = response.file; const newFile = response.file;
delete response.file; delete response.file;

View file

@ -12,7 +12,7 @@ const guildSettings = require("../lib/guildSettings.js");
function spawn(args) { function spawn(args) {
const shell = const shell =
process.env.SHELL || (process.platform == "win32" ? "powershell" : "bash"); process.env.SHELL || (process.platform == "win32" ? "powershell" : "sh");
const newArgs = []; const newArgs = [];
if (shell.match(/powershell/i) && process.platform == "win32") { if (shell.match(/powershell/i) && process.platform == "win32") {
@ -122,7 +122,7 @@ exec.category = CATEGORY;
exec.helpText = "Executes a command"; exec.helpText = "Executes a command";
exec.callback = async function (msg, line) { exec.callback = async function (msg, line) {
const proc = spawn(line); const proc = spawn(line);
let out = `Spawned ${proc.pid}: \`${line}'\n`; let out = `\x1b[1mSpawned ${proc.pid}: \`${line}'\x1b[0m\n`;
proc.stdout.on("data", (data) => { proc.stdout.on("data", (data) => {
out += data; out += data;
}); });
@ -131,7 +131,7 @@ exec.callback = async function (msg, line) {
}); });
proc.on("close", async (code) => { proc.on("close", async (code) => {
out += `====\nExited with ${code}`; out += `\x1b[1m====\n${code != 0 ? "\x1b[31m" : ""}Exited with ${code}`;
if (out.length > 1980) { if (out.length > 1980) {
const haste = await hastebin(out); const haste = await hastebin(out);
msg.channel.createMessage({ msg.channel.createMessage({
@ -145,7 +145,7 @@ exec.callback = async function (msg, line) {
}); });
} else { } else {
msg.channel.createMessage({ msg.channel.createMessage({
content: `\`\`\`\n${out}\`\`\``, content: `\`\`\`ansi\n${out}\`\`\``,
allowedMentions: { allowedMentions: {
repliedUser: false, repliedUser: false,
}, },
@ -161,7 +161,7 @@ hf.registerCommand(exec);
const settings = new Command("settings"); const settings = new Command("settings");
settings.category = CATEGORY; settings.category = CATEGORY;
settings.helpText = "Manage guild specific bot settings"; settings.helpText = "Manage guild specific bot settings";
settings.callback = async function (msg, line, cmd, key, value) { settings.callback = async function (msg, line, [cmd, key, value]) {
if (!msg.guildID) "This command only works in guilds."; if (!msg.guildID) "This command only works in guilds.";
switch (cmd) { switch (cmd) {

View file

@ -24,7 +24,7 @@ const utsuholights = new Command("utsuholights");
utsuholights.category = CATEGORY; utsuholights.category = CATEGORY;
utsuholights.helpText = "Utsuho Lights"; utsuholights.helpText = "Utsuho Lights";
utsuholights.usage = "<hex> [brightness]"; utsuholights.usage = "<hex> [brightness]";
utsuholights.callback = async function (msg, line, hex, bri) { utsuholights.callback = async function (msg, line, [hex, bri]) {
if (!hex) { if (!hex) {
return "Hex color required."; return "Hex color required.";
} }

View file

@ -33,7 +33,7 @@ const dumpy = new Command("dumpy");
dumpy.category = CATEGORY; dumpy.category = CATEGORY;
dumpy.helpText = "Among Us Dumpy GIF Creator"; dumpy.helpText = "Among Us Dumpy GIF Creator";
dumpy.usage = "<width> [url]"; dumpy.usage = "<width> [url]";
dumpy.callback = async function (msg, line, width, url) { dumpy.callback = async function (msg, line, [width, url]) {
if (isNaN(parseInt(width))) url = width; if (isNaN(parseInt(width))) url = width;
width = Math.min(Math.max(isNaN(parseInt(width)) ? 10 : width, 2), 48); width = Math.min(Math.max(isNaN(parseInt(width)) ? 10 : width, 2), 48);

View file

@ -76,13 +76,8 @@ wolfram.category = CATEGORY;
wolfram.helpText = "Wolfram Alpha"; wolfram.helpText = "Wolfram Alpha";
wolfram.usage = "<-v> [query]"; wolfram.usage = "<-v> [query]";
wolfram.addAlias("wa"); wolfram.addAlias("wa");
wolfram.callback = async function (msg, line) { wolfram.callback = async function (msg, line, [query], {verbose, v}) {
let verbose = false; const _verbose = verbose ?? v;
if (line.includes("-v")) {
line = line.replace("-v", "").trim();
verbose = true;
}
const req = await fetch( const req = await fetch(
`http://api.wolframalpha.com/v2/query?input=${encodeURIComponent( `http://api.wolframalpha.com/v2/query?input=${encodeURIComponent(
@ -97,7 +92,7 @@ wolfram.callback = async function (msg, line) {
// fake no answer // fake no answer
if (data[0].subpods[0].plaintext.includes("geoIP")) return WA_NO_ANSWER; if (data[0].subpods[0].plaintext.includes("geoIP")) return WA_NO_ANSWER;
if (verbose) { if (_verbose) {
const embed = { const embed = {
title: `Result for: \`${safeString(line)}\``, title: `Result for: \`${safeString(line)}\``,
fields: [], fields: [],
@ -225,7 +220,7 @@ const poll = new Command("poll");
poll.category = CATEGORY; poll.category = CATEGORY;
poll.helpText = "Start a poll"; poll.helpText = "Start a poll";
poll.usage = "[topic] [option 1] [option 2] [...option 3-10]"; poll.usage = "[topic] [option 1] [option 2] [...option 3-10]";
poll.callback = async function (msg, line, topic, ...options) { poll.callback = async function (msg, line, [topic, ...options]) {
if (!line || !topic) if (!line || !topic)
return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"'; return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"';
@ -254,15 +249,20 @@ const vote = new Command("vote");
vote.category = CATEGORY; vote.category = CATEGORY;
vote.helpText = "Start a yes/no vote"; vote.helpText = "Start a yes/no vote";
vote.usage = "[topic]"; vote.usage = "[topic]";
vote.callback = async function (msg, line) { vote.callback = async function (msg, line, [topic], {maybe}) {
if (!line) return "No topic given."; if (!topic) return "No topic given.";
return { return {
content: `**${msg.author.tag}** has started a vote:\n**__${line}__**\n<:ms_tick:503341995348066313>: Yes\n<:ms_cross:503341994974773250>: No`, content: `**${
msg.author.tag
}** has started a vote:\n**__${topic}__**\n<:ms_tick:503341995348066313>: Yes\n<:ms_cross:503341994974773250>: No${
maybe ? "\n<:ms_tilda:581268710925271095> Maybe/Uncertain" : ""
}`,
addReactions: [ addReactions: [
":ms_tick:503341995348066313", ":ms_tick:503341995348066313",
":ms_cross:503341994974773250", ":ms_cross:503341994974773250",
], maybe && ":ms_tilda:581268710925271095",
].filter((x) => x != null),
}; };
}; };
hf.registerCommand(vote); hf.registerCommand(vote);
@ -271,7 +271,7 @@ const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const anonradio = new Command("anonradio"); const anonradio = new Command("anonradio");
anonradio.category = CATEGORY; anonradio.category = CATEGORY;
anonradio.helpText = "aNONradio.net schedule"; anonradio.helpText = "aNONradio.net schedule";
anonradio.callback = async function (msg, line) { anonradio.callback = async function () {
const now = new Date(); const now = new Date();
const playing = await fetch("https://anonradio.net/playing").then((res) => const playing = await fetch("https://anonradio.net/playing").then((res) =>

View file

@ -9,7 +9,7 @@ tidy.addAlias("purge");
tidy.category = CATEGORY; tidy.category = CATEGORY;
tidy.helpText = "Clean up messages"; tidy.helpText = "Clean up messages";
tidy.usage = "[subcommand] <count> <extra>"; tidy.usage = "[subcommand] <count> <extra>";
tidy.callback = async function (msg, line, subcommand, count, extra) { tidy.callback = async function (msg, line, [subcommand, count, extra]) {
if (!msg.channel.permissionsOf(msg.author.id).has("MANAGE_MESSAGES")) { if (!msg.channel.permissionsOf(msg.author.id).has("MANAGE_MESSAGES")) {
return "You do not have `Manage Messages` permission."; return "You do not have `Manage Messages` permission.";
} }

View file

@ -66,8 +66,8 @@ const avatar = new Command("avatar");
avatar.category = CATEGORY; avatar.category = CATEGORY;
avatar.helpText = "Get avatar of a user"; avatar.helpText = "Get avatar of a user";
avatar.usage = "<user>"; avatar.usage = "<user>";
avatar.callback = async function (msg, line) { avatar.callback = async function (msg, line, [user], {server, guild}) {
if (line == "--server" || line == "--guild") { if (server || guild) {
if (!msg.guildID) { if (!msg.guildID) {
return "`--server/--guild` can only be used within guilds."; return "`--server/--guild` can only be used within guilds.";
} else { } else {
@ -87,23 +87,23 @@ avatar.callback = async function (msg, line) {
], ],
}; };
} }
} else if (line) { } else if (user) {
const user = await lookupUser(msg, line); const lookup = await lookupUser(msg, user);
if ( if (
user == "No results" || lookup == "No results" ||
user == "Canceled" || lookup == "Canceled" ||
user == "Request timed out" lookup == "Request timed out"
) { ) {
return user; return lookup;
} else { } else {
let member = user; let member = lookup;
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID); const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
if (guild) { if (guild) {
if (guild.members.has(user.id)) { if (guild.members.has(lookup.id)) {
member = guild.members.get(user.id); member = guild.members.get(lookup.id);
} else { } else {
const fetched = await guild.fetchMembers({ const fetched = await guild.fetchMembers({
userIDs: [user.id], userIDs: [lookup.id],
}); });
member = fetched[0]; member = fetched[0];
} }
@ -203,10 +203,10 @@ const banner = new Command("banner");
banner.category = CATEGORY; banner.category = CATEGORY;
banner.helpText = "Get banner of a user"; banner.helpText = "Get banner of a user";
banner.usage = "<user>"; banner.usage = "<user>";
banner.callback = async function (msg, line) { banner.callback = async function (msg, line, [user], {server, guild}) {
let id = msg.author.id; let id = msg.author.id;
if (line == "--server" || line == "--guild") { if (server || guild) {
if (!msg.guildID) { if (!msg.guildID) {
return "`--server/--guild` can only be used within guilds."; return "`--server/--guild` can only be used within guilds.";
} else { } else {
@ -229,8 +229,8 @@ banner.callback = async function (msg, line) {
], ],
}; };
} }
} else if (line) { } else if (user) {
const lookup = await lookupUser(msg, line); const lookup = await lookupUser(msg, user);
if ( if (
lookup == "No results" || lookup == "No results" ||
lookup == "Canceled" || lookup == "Canceled" ||
@ -390,13 +390,8 @@ const snowflake = new Command("snowflake");
snowflake.category = CATEGORY; snowflake.category = CATEGORY;
snowflake.helpText = "Converts a snowflake ID into readable time."; snowflake.helpText = "Converts a snowflake ID into readable time.";
snowflake.usage = "<--twitter> [snowflake]"; snowflake.usage = "<--twitter> [snowflake]";
snowflake.callback = function (msg, line) { snowflake.callback = function (msg, line, [snowflake], {twitter}) {
let twitter = false; const num = parseInt(snowflake);
if (line.startsWith("--twitter")) {
twitter = true;
line.replace("--twitter ", "");
}
const num = parseInt(line);
if (!isNaN(num)) { if (!isNaN(num)) {
let binary = num.toString(2); let binary = num.toString(2);
binary = "0".repeat(64 - binary.length) + binary; binary = "0".repeat(64 - binary.length) + binary;
@ -404,9 +399,9 @@ snowflake.callback = function (msg, line) {
parseInt(binary.substr(0, 42), 2) + parseInt(binary.substr(0, 42), 2) +
(twitter ? 1288834974657 : 1420070400000); (twitter ? 1288834974657 : 1420070400000);
return `The timestamp for \`${line}\` is ${new Date( return `The timestamp for \`${snowflake}\` is <t:${Math.floor(
timestamp timestamp / 1000
).toUTCString()}`; )}:F>`;
} else { } else {
return "Argument provided is not a number."; return "Argument provided is not a number.";
} }
@ -490,12 +485,13 @@ const flagdump = new Command("flagdump");
flagdump.category = CATEGORY; flagdump.category = CATEGORY;
flagdump.helpText = "Dumps Discord user flags."; flagdump.helpText = "Dumps Discord user flags.";
flagdump.usage = "[flags or user mention]"; flagdump.usage = "[flags or user mention]";
flagdump.callback = async function (msg, line) { flagdump.callback = async function (msg, line, [numOrMention], {id}) {
const num = parseInt(line); const num = Number(numOrMention);
if (/<@!?([0-9]*)>/.test(line)) { if (/<@!?(\d+)>/.test(numOrMention) || !isNaN(id)) {
const id = line.match(/<@!?([0-9]*)>/)[1]; const targetId = id || numOrMention.match(/<@!?(\d+)>/)?.[1];
if (!targetId) return "Got null ID.";
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID); const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
let user = guild && (await guild.fetchMembers({userIDs: [id]})); let user = guild && (await guild.fetchMembers({userIDs: [targetId]}));
if (!user || !user[0]) { if (!user || !user[0]) {
user = hf.bot.users.get(id); user = hf.bot.users.get(id);
} else { } else {