split misc, attempt to fix mcserver
This commit is contained in:
parent
78d759ad97
commit
538761fdf0
11 changed files with 1075 additions and 943 deletions
|
@ -1,951 +1,12 @@
|
||||||
const Command = require("../lib/command.js");
|
|
||||||
const InteractionCommand = require("../lib/interactionCommand.js");
|
|
||||||
const logger = require("../lib/logger.js");
|
|
||||||
const CATEGORY = "misc";
|
|
||||||
|
|
||||||
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
|
|
||||||
|
|
||||||
const {formatUsername, safeString} = require("../util/misc.js");
|
|
||||||
const {formatTime} = require("../util/time.js");
|
|
||||||
const {parseHtmlEntities} = require("../util/html.js");
|
|
||||||
|
|
||||||
const GoogleImages = require("google-images");
|
|
||||||
const {tinycolor, random: randomColor} = require("@ctrl/tinycolor");
|
|
||||||
const sharp = require("sharp");
|
|
||||||
const net = require("node:net");
|
|
||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
const {resolve} = require("node:path");
|
const {resolve} = require("node:path");
|
||||||
|
|
||||||
const imagesClient = new GoogleImages(hf.apikeys.gimg, hf.apikeys.google);
|
const logger = require("../lib/logger.js");
|
||||||
|
|
||||||
const yt = new Command("youtube");
|
for (const file of fs.readdirSync(resolve(__dirname, "misc"))) {
|
||||||
yt.addAlias("yt");
|
|
||||||
yt.category = CATEGORY;
|
|
||||||
yt.helpText = "Search YouTube";
|
|
||||||
yt.usage = "[search term]";
|
|
||||||
yt.callback = async function (msg, line) {
|
|
||||||
if (!line) return "Arguments are required.";
|
|
||||||
|
|
||||||
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
|
|
||||||
x.json()
|
|
||||||
);
|
|
||||||
|
|
||||||
const topVid = req.items[0];
|
|
||||||
|
|
||||||
let out = `**${safeString(parseHtmlEntities(topVid.title))}** | \`${safeString(
|
|
||||||
parseHtmlEntities(topVid.uploaderName)
|
|
||||||
)}\`\nhttps://youtube.com${topVid.url}\n\n**__See Also:__**\n`;
|
|
||||||
|
|
||||||
for (let i = 1; i < 5; i++) {
|
|
||||||
const vid = req.items[i];
|
|
||||||
if (!vid) continue;
|
|
||||||
|
|
||||||
out += `- **${safeString(parseHtmlEntities(vid.title))}** | By: \`${safeString(
|
|
||||||
parseHtmlEntities(vid.uploaderName)
|
|
||||||
)}\` | <https://youtube.com${vid.url}>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
hf.registerCommand(yt);
|
|
||||||
|
|
||||||
const ytInteraction = new InteractionCommand("youtube");
|
|
||||||
ytInteraction.helpText = "Search Youtube";
|
|
||||||
ytInteraction.options.search = {
|
|
||||||
name: "search",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "Search query",
|
|
||||||
required: true,
|
|
||||||
default: "",
|
|
||||||
};
|
|
||||||
ytInteraction.callback = async function (interaction) {
|
|
||||||
const search = this.getOption(interaction, "search");
|
|
||||||
|
|
||||||
return yt.callback(interaction, search);
|
|
||||||
};
|
|
||||||
hf.registerCommand(ytInteraction);
|
|
||||||
|
|
||||||
const fyt = new Command("fyt");
|
|
||||||
fyt.category = CATEGORY;
|
|
||||||
fyt.helpText = "Search YouTube and take the first result.";
|
|
||||||
fyt.usage = "[search term]";
|
|
||||||
fyt.callback = async function (msg, line) {
|
|
||||||
if (!line) return "Arguments are required.";
|
|
||||||
|
|
||||||
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
|
|
||||||
x.json()
|
|
||||||
);
|
|
||||||
|
|
||||||
const vid = req.items[0];
|
|
||||||
|
|
||||||
return `**${safeString(parseHtmlEntities(vid.title))}** | \`${safeString(
|
|
||||||
parseHtmlEntities(vid.uploaderName)
|
|
||||||
)}\`\nhttps://youtube.com${vid.url}`;
|
|
||||||
};
|
|
||||||
hf.registerCommand(fyt);
|
|
||||||
|
|
||||||
const fytInteraction = new InteractionCommand("fyt");
|
|
||||||
fytInteraction.helpText = "Search Youtube and take the first result.";
|
|
||||||
fytInteraction.options.search = {
|
|
||||||
name: "search",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "Search query",
|
|
||||||
required: true,
|
|
||||||
default: "",
|
|
||||||
};
|
|
||||||
fytInteraction.callback = async function (interaction) {
|
|
||||||
const search = this.getOption(interaction, "search");
|
|
||||||
|
|
||||||
return fyt.callback(interaction, search);
|
|
||||||
};
|
|
||||||
hf.registerCommand(fytInteraction);
|
|
||||||
|
|
||||||
const WA_NO_ANSWER = "<:ms_cross:503341994974773250> No answer.";
|
|
||||||
|
|
||||||
const wolfram = new Command("wolfram");
|
|
||||||
wolfram.category = CATEGORY;
|
|
||||||
wolfram.helpText = "Wolfram Alpha";
|
|
||||||
wolfram.usage = "<-v> [query]";
|
|
||||||
wolfram.addAlias("wa");
|
|
||||||
wolfram.addAlias("calc");
|
|
||||||
wolfram.callback = async function (msg, line, args, {verbose, v}) {
|
|
||||||
const _verbose = verbose ?? v;
|
|
||||||
const query = args.join(" ");
|
|
||||||
|
|
||||||
const req = await fetch(
|
|
||||||
`http://api.wolframalpha.com/v2/query?input=${encodeURIComponent(query)}&appid=LH2K8H-T3QKETAGT3&output=json`
|
|
||||||
).then((x) => x.json());
|
|
||||||
|
|
||||||
const data = req.queryresult.pods;
|
|
||||||
|
|
||||||
if (!data) return WA_NO_ANSWER;
|
|
||||||
|
|
||||||
// fake no answer
|
|
||||||
//if (data[0].subpods[0].plaintext.includes("geoIP")) return WA_NO_ANSWER;
|
|
||||||
|
|
||||||
if (_verbose) {
|
|
||||||
const embed = {
|
|
||||||
title: `Result for: \`${safeString(query)}\``,
|
|
||||||
fields: [],
|
|
||||||
footer: {
|
|
||||||
icon_url: "http://www.wolframalpha.com/share.png",
|
|
||||||
text: "Powered by Wolfram Alpha",
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
url: data[1].subpods[0].img.src,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const extra = data.slice(1, 6);
|
|
||||||
for (const x in extra) {
|
|
||||||
embed.fields.push({
|
|
||||||
name: extra[x].title,
|
|
||||||
value: `[${extra[x].subpods[0].plaintext.length > 0 ? extra[x].subpods[0].plaintext : "<click for image>"}](${
|
|
||||||
extra[x].subpods[0].img.src
|
|
||||||
})`,
|
|
||||||
inline: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {embed};
|
|
||||||
} else {
|
|
||||||
let image;
|
|
||||||
|
|
||||||
if (data[1].subpods[0].img.src)
|
|
||||||
try {
|
|
||||||
const res = await fetch(data[1].subpods[0].img.src);
|
|
||||||
if (res) {
|
|
||||||
const imgData = await res.arrayBuffer();
|
|
||||||
image = Buffer.from(imgData);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
let string = "";
|
|
||||||
if (data[1].subpods[0].plaintext.length > 0) string = safeString(data[1].subpods[0].plaintext);
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (string.length > 2000 - (6 + safeString(query).length)) {
|
|
||||||
text = string;
|
|
||||||
string = "Output too long:";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: `\`${safeString(query)}\` -> ${string.length > 0 ? string : ""}`,
|
|
||||||
attachments: [
|
|
||||||
text && {
|
|
||||||
file: text,
|
|
||||||
filename: "message.txt",
|
|
||||||
},
|
|
||||||
image && {
|
|
||||||
file: image,
|
|
||||||
filename: "wolfram_output.gif",
|
|
||||||
},
|
|
||||||
].filter((x) => !!x),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
hf.registerCommand(wolfram);
|
|
||||||
|
|
||||||
const wolframInteraction = new InteractionCommand("wolfram");
|
|
||||||
wolframInteraction.helpText = "Wolfram Alpha";
|
|
||||||
wolframInteraction.options.query = {
|
|
||||||
name: "query",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "What to query Wolfram Alpha for",
|
|
||||||
required: true,
|
|
||||||
default: "",
|
|
||||||
};
|
|
||||||
wolframInteraction.options.verbose = {
|
|
||||||
name: "verbose",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "Verbose output",
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
};
|
|
||||||
wolframInteraction.callback = async function (interaction) {
|
|
||||||
const query = this.getOption(interaction, "query");
|
|
||||||
const verbose = this.getOption(interaction, "verbose");
|
|
||||||
|
|
||||||
return wolfram.callback(interaction, query, [query], {verbose});
|
|
||||||
};
|
|
||||||
hf.registerCommand(wolframInteraction);
|
|
||||||
|
|
||||||
const gimg = new Command("gimg");
|
|
||||||
gimg.category = CATEGORY;
|
|
||||||
gimg.helpText = "Search Google Images";
|
|
||||||
gimg.usage = "[query]";
|
|
||||||
gimg.addAlias("img");
|
|
||||||
gimg.callback = async function (msg, line) {
|
|
||||||
if (!line) return "No arguments given.";
|
|
||||||
|
|
||||||
const images = await imagesClient.search(line, {
|
|
||||||
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
|
|
||||||
});
|
|
||||||
|
|
||||||
const index = Math.floor(Math.random() * images.length);
|
|
||||||
const image = images[index];
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: image.description,
|
|
||||||
url: image.parentPage,
|
|
||||||
image: {
|
|
||||||
url: image.url,
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
text: `Image ${Number(index) + 1}/${images.length}. Rerun to get a different image.`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(gimg);
|
|
||||||
|
|
||||||
const fimg = new Command("fimg");
|
|
||||||
fimg.category = CATEGORY;
|
|
||||||
fimg.helpText = "Send first result from Google Images";
|
|
||||||
fimg.usage = "[query]";
|
|
||||||
fimg.callback = async function (msg, line) {
|
|
||||||
if (!line) return "No arguments given.";
|
|
||||||
|
|
||||||
const images = await imagesClient.search(line, {
|
|
||||||
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
|
|
||||||
});
|
|
||||||
|
|
||||||
const image = images[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: image.description,
|
|
||||||
url: image.parentPage,
|
|
||||||
image: {
|
|
||||||
url: image.url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(fimg);
|
|
||||||
|
|
||||||
const poll = new Command("poll");
|
|
||||||
poll.category = CATEGORY;
|
|
||||||
poll.helpText = "Start a poll";
|
|
||||||
poll.usage = "[topic] [option 1] [option 2] [...option 3-10]";
|
|
||||||
poll.callback = async function (msg, line, [topic, ...options]) {
|
|
||||||
if (!line || !topic) return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"';
|
|
||||||
|
|
||||||
const arrOptions = [...options].slice(0, 10);
|
|
||||||
if (arrOptions.length < 2) return "A minimum of two options are required.";
|
|
||||||
|
|
||||||
const reactions = [];
|
|
||||||
let displayString = `**${formatUsername(msg.author)}** has started a poll:\n## __${topic}__\n`;
|
|
||||||
for (let i = 0; i < arrOptions.length; i++) {
|
|
||||||
displayString += (i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`) + ": " + arrOptions[i] + "\n";
|
|
||||||
reactions[i] = i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: displayString,
|
|
||||||
addReactions: reactions,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(poll);
|
|
||||||
|
|
||||||
const vote = new Command("vote");
|
|
||||||
vote.category = CATEGORY;
|
|
||||||
vote.helpText = "Start a yes/no vote";
|
|
||||||
vote.usage = "[topic]";
|
|
||||||
vote.callback = async function (msg, line, topic, {maybe}) {
|
|
||||||
if (!topic) return "No topic given.";
|
|
||||||
topic = topic.join(" ");
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: `**${formatUsername(
|
|
||||||
msg.author
|
|
||||||
)}** has started a vote:\n## __${topic}__\n<:ms_tick:503341995348066313>: Yes\n<:ms_cross:503341994974773250>: No${
|
|
||||||
maybe ? "\n<:ms_tilda:581268710925271095>: Maybe/Uncertain" : ""
|
|
||||||
}`,
|
|
||||||
addReactions: [
|
|
||||||
":ms_tick:503341995348066313",
|
|
||||||
":ms_cross:503341994974773250",
|
|
||||||
maybe && ":ms_tilda:581268710925271095",
|
|
||||||
].filter((x) => x != null),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(vote);
|
|
||||||
|
|
||||||
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
||||||
const anonradio = new Command("anonradio");
|
|
||||||
anonradio.category = CATEGORY;
|
|
||||||
anonradio.helpText = "aNONradio.net schedule";
|
|
||||||
anonradio.callback = async function () {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
let playing;
|
|
||||||
try {
|
try {
|
||||||
playing = await fetch("https://anonradio.net/playing").then((res) => res.text());
|
require(resolve(__dirname, "misc", file));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
logger.error("hf:modules:misc", `Failed to load "${file}": ${err}`);
|
||||||
playing = await fetch("http://anonradio.net/playing").then((res) => res.text());
|
|
||||||
} catch (err) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let schedule;
|
|
||||||
try {
|
|
||||||
schedule = await fetch("https://anonradio.net/schedule/").then((res) => res.text());
|
|
||||||
} catch (err) {
|
|
||||||
try {
|
|
||||||
schedule = await fetch("http://anonradio.net/schedule/").then((res) => res.text());
|
|
||||||
} catch (err) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playing || !schedule) return "Failed to fetch schedule.";
|
|
||||||
|
|
||||||
const icecast = await fetch("http://anonradio.net:8010/status-json.xsl")
|
|
||||||
.then((res) => res.text())
|
|
||||||
.then((data) => JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",')));
|
|
||||||
|
|
||||||
let lines = schedule.split("\n");
|
|
||||||
lines = lines.slice(4, lines.length - 2);
|
|
||||||
|
|
||||||
const parsedLines = [];
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const [_, time, id, name] = line.match(/^(.{3,4} .{4})\s+(.+?) {2}(.+?)$/);
|
|
||||||
|
|
||||||
const tmp = time.split(" ");
|
|
||||||
const day = tmp[0];
|
|
||||||
let hour = tmp[1];
|
|
||||||
const currentDay = now.getUTCDay();
|
|
||||||
const targetDay = DAYS.indexOf(day);
|
|
||||||
const delta = (targetDay + 7 - currentDay) % 7;
|
|
||||||
|
|
||||||
let currentYear = now.getUTCFullYear();
|
|
||||||
const currentMonth = now.getUTCMonth() + 1;
|
|
||||||
const currentDateDay = now.getUTCDate();
|
|
||||||
let targetMonth = currentMonth;
|
|
||||||
const lastDay = new Date(currentYear, currentMonth, 0).getDate();
|
|
||||||
let targetDateDay = currentDateDay + delta;
|
|
||||||
if (targetDateDay > lastDay) {
|
|
||||||
targetMonth = currentMonth === 12 ? 1 : currentMonth + 1;
|
|
||||||
targetDateDay = 1;
|
|
||||||
if (currentMonth === 12) currentYear++;
|
|
||||||
}
|
|
||||||
|
|
||||||
hour = hour.slice(0, 2) + ":" + hour.slice(-2);
|
|
||||||
|
|
||||||
const timestamp =
|
|
||||||
Date.parse(`${DAYS[targetDay]}, ${currentYear}-${targetMonth}-${targetDateDay} ${hour} UTC`) / 1000;
|
|
||||||
|
|
||||||
let nameOut = name;
|
|
||||||
|
|
||||||
if (time == "Sat 0300") nameOut = name.replace("Open Mic - Anyone can stream", "Synth Battle Royale");
|
|
||||||
|
|
||||||
parsedLines.push({
|
|
||||||
timestamp,
|
|
||||||
id,
|
|
||||||
name: nameOut.replace(" <- Up NEXT", ""),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let liveNow = {name: "ident", id: "aNONradio"};
|
|
||||||
|
|
||||||
if (parsedLines[0].name.includes("<- Live NOW")) {
|
|
||||||
liveNow = parsedLines.splice(0, 1)[0];
|
|
||||||
liveNow.name = liveNow.name.replace(" <- Live NOW", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = "";
|
|
||||||
let subtitle = "";
|
|
||||||
|
|
||||||
if (playing.includes("listeners with a daily peak of")) {
|
|
||||||
title = `${liveNow.name} (\`${liveNow.id}\`)`;
|
|
||||||
subtitle = playing;
|
|
||||||
} else {
|
|
||||||
const [_, current, peakDay, peakMonth, dj, metadata] = playing.match(/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/);
|
|
||||||
|
|
||||||
if (metadata == "https://archives.anonradio.net" || liveNow.name == "Synth Battle Royale") {
|
|
||||||
title = `${liveNow.name} (\`${dj}\`)`;
|
|
||||||
} else {
|
|
||||||
title = `${metadata} (\`${dj}\`)`;
|
|
||||||
}
|
|
||||||
subtitle = `${current} listening with a daily peak of ${peakDay} and ${peakMonth} peak for the month.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let openmicTime = "";
|
|
||||||
if (liveNow.id == "openmic") {
|
|
||||||
const streamData = icecast.icestats.source.find((src) => src.listenurl == "http://anonradio.net:8010/openmic");
|
|
||||||
if (streamData && streamData.stream_start_iso8601) {
|
|
||||||
const startTime = new Date(streamData.stream_start_iso8601).getTime();
|
|
||||||
openmicTime = `-\\*- OpenMIC DJ has been streaming for ${formatTime(Date.now() - startTime)} -\\*-\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: subtitle,
|
|
||||||
author: {
|
|
||||||
name: title,
|
|
||||||
url: "http://anonradio.net:8000/anonradio",
|
|
||||||
},
|
|
||||||
description: openmicTime + "__Schedule:__",
|
|
||||||
fields: parsedLines.map((line) => ({
|
|
||||||
inline: true,
|
|
||||||
name: `${line.name} (\`${line.id}\`)`,
|
|
||||||
value: `<t:${line.timestamp}:R>`,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(anonradio);
|
|
||||||
|
|
||||||
const REGEX_IPV4 = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
|
||||||
const shodan = new Command("shodan");
|
|
||||||
shodan.category = CATEGORY;
|
|
||||||
shodan.helpText = "Look up an IP on Shodan InternetDB";
|
|
||||||
shodan.callback = async function (msg, line) {
|
|
||||||
if (!line || line == "") return "Arguments required.";
|
|
||||||
if (!REGEX_IPV4.test(line)) return "Invalid IP address.";
|
|
||||||
|
|
||||||
const data = await fetch("https://internetdb.shodan.io/" + line).then((res) => res.json());
|
|
||||||
|
|
||||||
if (data.detail) return data.detail;
|
|
||||||
|
|
||||||
return {
|
|
||||||
embed: {
|
|
||||||
title: `Results for \`${data.ip}\``,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Hostnames",
|
|
||||||
value: data.hostnames.length > 0 ? data.hostnames.map((str) => `\`${str}\``).join(" ") : "None",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Open ports",
|
|
||||||
value: data.ports.length > 0 ? data.ports.join(", ") : "None",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Tags",
|
|
||||||
value: data.tags.length > 0 ? data.tags.map((str) => `\`${str}\``).join(", ") : "None",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "CPEs",
|
|
||||||
value: data.cpes.length > 0 ? data.cpes.map((str) => `\`${str}\``).join(" ") : "None",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Vulnerabilities",
|
|
||||||
value: data.vulns.length > 0 ? data.vulns.map((str) => `\`${str}\``).join(" ") : "None",
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(shodan);
|
|
||||||
|
|
||||||
const GENERATE_HEADERS = {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
const generate = new Command("generate");
|
|
||||||
generate.category = CATEGORY;
|
|
||||||
generate.helpText = "Generate images from prompt via craiyon";
|
|
||||||
generate.callback = async function (msg, line) {
|
|
||||||
if (!line || line.length === 0) return "Arguments required.";
|
|
||||||
msg.channel.sendTyping();
|
|
||||||
|
|
||||||
const start = Date.now();
|
|
||||||
let retries = 0;
|
|
||||||
|
|
||||||
let request = await fetch("https://backend.craiyon.com/generate", {
|
|
||||||
method: "POST",
|
|
||||||
headers: GENERATE_HEADERS,
|
|
||||||
body: JSON.stringify({prompt: line}),
|
|
||||||
});
|
|
||||||
while (request.status !== 200) {
|
|
||||||
request = await fetch("https://backend.craiyon.com/generate", {
|
|
||||||
method: "POST",
|
|
||||||
headers: GENERATE_HEADERS,
|
|
||||||
body: JSON.stringify({prompt: line}),
|
|
||||||
});
|
|
||||||
retries++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await request.json();
|
|
||||||
const images = data.images
|
|
||||||
.map((img) => Buffer.from(img, "base64"))
|
|
||||||
.map((img, index) => ({contents: img, name: `${index}.jpg`}));
|
|
||||||
|
|
||||||
const title = `Responses for "${safeString(line)}"`;
|
|
||||||
|
|
||||||
const out = {
|
|
||||||
content: `Generated in ${formatTime(Date.now() - start)}${retries > 0 ? " with " + retries + " retries" : ""}`,
|
|
||||||
embeds: [],
|
|
||||||
files: images,
|
|
||||||
};
|
|
||||||
|
|
||||||
let splitIndex = 0;
|
|
||||||
for (const index in images) {
|
|
||||||
if (index % 3 == 0) splitIndex++;
|
|
||||||
out.embeds.push({
|
|
||||||
title,
|
|
||||||
url: "https://www.craiyon.com/?" + splitIndex,
|
|
||||||
image: {
|
|
||||||
url: `attachment://${index}.jpg`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
hf.registerCommand(generate);
|
|
||||||
|
|
||||||
const search = new Command("search");
|
|
||||||
search.category = CATEGORY;
|
|
||||||
search.helpText = "Search, powered by LibreX";
|
|
||||||
search.addAlias("g");
|
|
||||||
search.addAlias("google");
|
|
||||||
search.addAlias("ddg");
|
|
||||||
search.callback = async function (msg, line, args, {results = 2}) {
|
|
||||||
const query = args.join(" ");
|
|
||||||
if (!hf.config.librex) return "LibreX instance not defined.";
|
|
||||||
if (!query || query == "") return "Search query required.";
|
|
||||||
|
|
||||||
const encodedQuery = encodeURIComponent(query);
|
|
||||||
|
|
||||||
if (query.startsWith("!")) {
|
|
||||||
const url = `https://api.duckduckgo.com/?q=${encodedQuery}&format=json`;
|
|
||||||
const res = await fetch(url);
|
|
||||||
if (res.url != url) return res.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${hf.config.librex}/api.php?q=${encodedQuery}&p=0&t=0`).then((res) => res.json());
|
|
||||||
if (res.error?.message) {
|
|
||||||
if (res.error.message.indexOf("No results found.") > -1) {
|
|
||||||
return "Search returned no results.";
|
|
||||||
} else {
|
|
||||||
return `Search returned error:\n\`\`\`\n${res.error.message}\`\`\``;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const searchResults = Object.values(res)
|
|
||||||
.filter((result) => result.did_you_mean == null && typeof result !== "string")
|
|
||||||
.splice(0, Number(results));
|
|
||||||
|
|
||||||
if (searchResults.length > 0) {
|
|
||||||
let out = `__**Results for \`${safeString(query)}\`**__\n`;
|
|
||||||
for (const result of searchResults) {
|
|
||||||
if (result.special_response) {
|
|
||||||
out += "> " + safeString(parseHtmlEntities(result.special_response.response.split("\n").join("\n> ")));
|
|
||||||
out += `\n<${encodeURI(result.special_response.source)}>`;
|
|
||||||
} else {
|
|
||||||
out += `**${safeString(parseHtmlEntities(result.title)).trim()}** - <${encodeURI(result.url)}>`;
|
|
||||||
out += `\n> ${safeString(parseHtmlEntities(result.description))}`;
|
|
||||||
}
|
|
||||||
out += "\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.trim();
|
|
||||||
} else {
|
|
||||||
return "Search returned no results.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
hf.registerCommand(search);
|
|
||||||
|
|
||||||
const searchInteraction = new InteractionCommand("search");
|
|
||||||
searchInteraction.helpText = "Search, powered by LibreX";
|
|
||||||
searchInteraction.options.query = {
|
|
||||||
name: "query",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "What to search for",
|
|
||||||
required: true,
|
|
||||||
default: "",
|
|
||||||
};
|
|
||||||
searchInteraction.options.results = {
|
|
||||||
name: "results",
|
|
||||||
type: ApplicationCommandOptionTypes.INTEGER,
|
|
||||||
description: "How many results to show",
|
|
||||||
required: false,
|
|
||||||
min_value: 1,
|
|
||||||
max_value: 10,
|
|
||||||
default: 2,
|
|
||||||
};
|
|
||||||
searchInteraction.callback = async function (interaction) {
|
|
||||||
const query = this.getOption(interaction, "query");
|
|
||||||
const results = this.getOption(interaction, "results");
|
|
||||||
|
|
||||||
return search.callback(interaction, query, [query], {results});
|
|
||||||
};
|
|
||||||
hf.registerCommand(searchInteraction);
|
|
||||||
|
|
||||||
const colornamesRaw = fs.readFileSync(resolve(__dirname, "../../data/colornames.csv"), "utf8");
|
|
||||||
|
|
||||||
const color = new Command("color");
|
|
||||||
color.category = CATEGORY;
|
|
||||||
color.helpText = "Show information on a color or get a random color";
|
|
||||||
color.callback = async function (msg, line, args, {truerandom, first}) {
|
|
||||||
const query = args.join(" ");
|
|
||||||
let color = tinycolor(query),
|
|
||||||
random = false;
|
|
||||||
|
|
||||||
if (!query || query == "") {
|
|
||||||
color = truerandom ? tinycolor(Math.floor(Math.random() * 0xffffff)) : randomColor();
|
|
||||||
random = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!color.isValid) {
|
|
||||||
const search = `^([0-9a-f]{6}),${query},`;
|
|
||||||
if (!first) {
|
|
||||||
const colornamesMatches = colornamesRaw.match(new RegExp(search, "img"));
|
|
||||||
if (colornamesMatches.length > 1) {
|
|
||||||
const hexes = colornamesMatches.map((m) => m.split(",")[0]);
|
|
||||||
const random = hexes[Math.floor(Math.random() * hexes.length)];
|
|
||||||
|
|
||||||
const left = "- #" + hexes.slice(0, Math.ceil(hexes.length / 2)).join("\n- #");
|
|
||||||
const right = "- #" + hexes.slice(Math.ceil(hexes.length / 2), hexes.length).join("\n- #");
|
|
||||||
|
|
||||||
return {
|
|
||||||
embed: {
|
|
||||||
color: parseInt(`0x${random}`),
|
|
||||||
footer: {
|
|
||||||
text: `Picked #${random} for embed color \u2022 Use \`--first\` or the \`first\` option to bypass this`,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{name: `Got ${colornamesMatches.length} matches for \`${safeString(query)}\``, value: left, inline: true},
|
|
||||||
{name: "\u200b", value: right, inline: true},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const colornamesHex = colornamesRaw.match(new RegExp(search, "im"))?.[1];
|
|
||||||
if (colornamesHex) {
|
|
||||||
color = tinycolor(colornamesHex);
|
|
||||||
} else {
|
|
||||||
return "Color not valid.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = await sharp({
|
|
||||||
create: {
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
channels: 3,
|
|
||||||
background: {r: color.r, g: color.g, b: color.b},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
const hex = color.toHex();
|
|
||||||
const fileName = `${hex}.png`;
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{
|
|
||||||
name: "Hex",
|
|
||||||
value: color.toHexString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "RGB",
|
|
||||||
value: color.toRgbString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "HSL",
|
|
||||||
value: color.toHslString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "HSV",
|
|
||||||
value: color.toHsvString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Integer",
|
|
||||||
value: color.toNumber(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (color.toName() != false) {
|
|
||||||
fields.splice(0, 0, {
|
|
||||||
name: "Name",
|
|
||||||
value: color.toName(),
|
|
||||||
inline: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const colornamesName = colornamesRaw.match(new RegExp(`^${hex},([^,]+?),`, "im"))?.[1];
|
|
||||||
if (colornamesName) {
|
|
||||||
fields.push({
|
|
||||||
name: "\u200b",
|
|
||||||
value: `**[colornames](https://colornames.org/color/${hex}) Name**\n${colornamesName}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: random ? "Random Color" : "",
|
|
||||||
color: color.toNumber(),
|
|
||||||
thumbnail: {
|
|
||||||
url: `attachment://${fileName}`,
|
|
||||||
},
|
|
||||||
fields,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
file: {
|
|
||||||
file: image,
|
|
||||||
name: fileName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hf.registerCommand(color);
|
|
||||||
|
|
||||||
const colorInteraction = new InteractionCommand("color");
|
|
||||||
colorInteraction.helpText = "Show information on a color or get a random color";
|
|
||||||
colorInteraction.options.input = {
|
|
||||||
name: "input",
|
|
||||||
type: ApplicationCommandOptionTypes.STRING,
|
|
||||||
description: "Color to get info on",
|
|
||||||
required: false,
|
|
||||||
default: "",
|
|
||||||
};
|
|
||||||
colorInteraction.options.truerandom = {
|
|
||||||
name: "truerandom",
|
|
||||||
type: ApplicationCommandOptionTypes.BOOLEAN,
|
|
||||||
description: "Should the random color give a 'true random' color instead of an adjust color",
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
};
|
|
||||||
colorInteraction.options.first = {
|
|
||||||
name: "first",
|
|
||||||
type: ApplicationCommandOptionTypes.BOOLEAN,
|
|
||||||
description: "Take first match for a colornames color by name",
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
};
|
|
||||||
colorInteraction.callback = async function (interaction) {
|
|
||||||
const input = this.getOption(interaction, "input");
|
|
||||||
const truerandom = this.getOption(interaction, "truerandom");
|
|
||||||
const first = this.getOption(interaction, "first");
|
|
||||||
|
|
||||||
return color.callback(interaction, input, [input], {truerandom, first});
|
|
||||||
};
|
|
||||||
hf.registerCommand(colorInteraction);
|
|
||||||
|
|
||||||
function writeVarInt(value) {
|
|
||||||
let buf = Buffer.alloc(0);
|
|
||||||
|
|
||||||
do {
|
|
||||||
let temp = value & 0b01111111;
|
|
||||||
|
|
||||||
value >>>= 7;
|
|
||||||
|
|
||||||
if (value != 0) {
|
|
||||||
temp |= 0b10000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = Buffer.concat([buf, Buffer.from([temp])]);
|
|
||||||
} while (value != 0);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ident = Buffer.from("HiddenPhox (c7.pm) ", "utf8");
|
|
||||||
const identPort = Buffer.alloc(2);
|
|
||||||
identPort.writeUInt16BE(3);
|
|
||||||
|
|
||||||
const handshake = Buffer.concat([
|
|
||||||
writeVarInt(0x0),
|
|
||||||
writeVarInt(1073741953),
|
|
||||||
writeVarInt(ident.length),
|
|
||||||
ident,
|
|
||||||
identPort,
|
|
||||||
writeVarInt(1),
|
|
||||||
]);
|
|
||||||
const handshakeWithLength = Buffer.concat([writeVarInt(handshake.length), handshake]);
|
|
||||||
|
|
||||||
const status = Buffer.concat([writeVarInt(1), writeVarInt(0x0)]);
|
|
||||||
|
|
||||||
const HANDSHAKE_PACKET = Buffer.concat([handshakeWithLength, status]);
|
|
||||||
|
|
||||||
const formattingToAnsi = {
|
|
||||||
r: "0",
|
|
||||||
l: "1",
|
|
||||||
m: "9",
|
|
||||||
n: "4",
|
|
||||||
o: "3",
|
|
||||||
0: "30",
|
|
||||||
1: "34",
|
|
||||||
2: "32",
|
|
||||||
3: "36",
|
|
||||||
4: "31",
|
|
||||||
5: "35",
|
|
||||||
6: "33",
|
|
||||||
7: "37",
|
|
||||||
8: "90",
|
|
||||||
9: "94",
|
|
||||||
a: "92",
|
|
||||||
b: "96",
|
|
||||||
c: "91",
|
|
||||||
d: "95",
|
|
||||||
e: "93",
|
|
||||||
f: "97",
|
|
||||||
};
|
|
||||||
|
|
||||||
const mcserver = new Command("mcserver");
|
|
||||||
mcserver.category = CATEGORY;
|
|
||||||
mcserver.helpText = "Query a Minecraft server";
|
|
||||||
mcserver.callback = async function (msg, line) {
|
|
||||||
if (!line || line == "") return "Arguments required.";
|
|
||||||
|
|
||||||
const split = line.split(":");
|
|
||||||
const ip = split[0];
|
|
||||||
const port = split[1] ?? 25565;
|
|
||||||
|
|
||||||
await msg.addReaction("\uD83C\uDFD3");
|
|
||||||
|
|
||||||
const data = await new Promise((resolve, reject) => {
|
|
||||||
logger.verbose("mcserver", "querying", ip, port);
|
|
||||||
|
|
||||||
const client = net.createConnection(
|
|
||||||
{
|
|
||||||
host: ip,
|
|
||||||
port: port,
|
|
||||||
timeout: 180000,
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
logger.verbose("mcserver", "connect");
|
|
||||||
client.write(HANDSHAKE_PACKET);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
logger.verbose("mcserver", "timeout");
|
|
||||||
client.destroy();
|
|
||||||
resolve("timeout");
|
|
||||||
}, 180000);
|
|
||||||
|
|
||||||
let totalData = Buffer.alloc(0);
|
|
||||||
client.on("data", function (data) {
|
|
||||||
totalData = Buffer.concat([totalData, data]);
|
|
||||||
logger.verbose("mcserver", "data", data.length, totalData.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("close", function (err) {
|
|
||||||
if (err) {
|
|
||||||
logger.verbose("mcserver", "close with error", err);
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
const dataAsString = totalData.toString().trim();
|
|
||||||
console.log(dataAsString);
|
|
||||||
const json = JSON.parse(dataAsString.slice(dataAsString.indexOf("{"), dataAsString.lastIndexOf("}") + 1));
|
|
||||||
logger.verbose("mcserver", "close", json);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
return resolve(json);
|
|
||||||
});
|
|
||||||
client.on("timeout", function () {});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data == "timeout") {
|
|
||||||
await msg.removeReaction("\uD83C\uDFD3");
|
|
||||||
return "Timed out trying to query.";
|
|
||||||
} else {
|
|
||||||
await msg.removeReaction("\uD83C\uDFD3");
|
|
||||||
|
|
||||||
const motd = data.description.text.replace(/\u00a7([a-f0-9k-or])/gi, (formatting) => {
|
|
||||||
const ansi = formattingToAnsi[formatting];
|
|
||||||
return ansi ? `\x1b[${ansi}m` : "";
|
|
||||||
});
|
|
||||||
|
|
||||||
const players = data.players?.sample?.map((player) => player.name) ?? [];
|
|
||||||
const totalPlayers = `(${data.players.online}/${data.players.max})`;
|
|
||||||
let image;
|
|
||||||
if (data.favicon) {
|
|
||||||
image = Buffer.from(data.favicon.slice(data.favicon.indexOf(",")), "base64");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
embed: {
|
|
||||||
title: `Server info for: \`${line}\``,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "MOTD",
|
|
||||||
value: `\`\`\`ansi\n${motd}\n\`\`\``,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Version",
|
|
||||||
value: `${data.version.name} (\`${data.version.protocol}\`)`,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Players ${players.length > 0 ? totalPlayers : ""}`,
|
|
||||||
value: players.length > 0 ? players.join(", ") : totalPlayers,
|
|
||||||
inline: players.length == 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
thumbnail: image && {
|
|
||||||
url: "attachment://icon.png",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
file: image && {
|
|
||||||
file: image,
|
|
||||||
name: "icon.png",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
hf.registerCommand(mcserver);
|
|
||||||
|
|
133
src/modules/misc/anonradio.js
Normal file
133
src/modules/misc/anonradio.js
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
|
||||||
|
const {formatTime} = require("../util/time.js");
|
||||||
|
|
||||||
|
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
const anonradio = new Command("anonradio");
|
||||||
|
anonradio.category = "misc";
|
||||||
|
anonradio.helpText = "aNONradio.net schedule";
|
||||||
|
anonradio.callback = async function () {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
let playing;
|
||||||
|
try {
|
||||||
|
playing = await fetch("https://anonradio.net/playing").then((res) => res.text());
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
playing = await fetch("http://anonradio.net/playing").then((res) => res.text());
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let schedule;
|
||||||
|
try {
|
||||||
|
schedule = await fetch("https://anonradio.net/schedule/").then((res) => res.text());
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
schedule = await fetch("http://anonradio.net/schedule/").then((res) => res.text());
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playing || !schedule) return "Failed to fetch schedule.";
|
||||||
|
|
||||||
|
const icecast = await fetch("http://anonradio.net:8010/status-json.xsl")
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((data) => JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",')));
|
||||||
|
|
||||||
|
let lines = schedule.split("\n");
|
||||||
|
lines = lines.slice(4, lines.length - 2);
|
||||||
|
|
||||||
|
const parsedLines = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const [_, time, id, name] = line.match(/^(.{3,4} .{4})\s+(.+?) {2}(.+?)$/);
|
||||||
|
|
||||||
|
const tmp = time.split(" ");
|
||||||
|
const day = tmp[0];
|
||||||
|
let hour = tmp[1];
|
||||||
|
const currentDay = now.getUTCDay();
|
||||||
|
const targetDay = DAYS.indexOf(day);
|
||||||
|
const delta = (targetDay + 7 - currentDay) % 7;
|
||||||
|
|
||||||
|
let currentYear = now.getUTCFullYear();
|
||||||
|
const currentMonth = now.getUTCMonth() + 1;
|
||||||
|
const currentDateDay = now.getUTCDate();
|
||||||
|
let targetMonth = currentMonth;
|
||||||
|
const lastDay = new Date(currentYear, currentMonth, 0).getDate();
|
||||||
|
let targetDateDay = currentDateDay + delta;
|
||||||
|
if (targetDateDay > lastDay) {
|
||||||
|
targetMonth = currentMonth === 12 ? 1 : currentMonth + 1;
|
||||||
|
targetDateDay = 1;
|
||||||
|
if (currentMonth === 12) currentYear++;
|
||||||
|
}
|
||||||
|
|
||||||
|
hour = hour.slice(0, 2) + ":" + hour.slice(-2);
|
||||||
|
|
||||||
|
const timestamp =
|
||||||
|
Date.parse(`${DAYS[targetDay]}, ${currentYear}-${targetMonth}-${targetDateDay} ${hour} UTC`) / 1000;
|
||||||
|
|
||||||
|
let nameOut = name;
|
||||||
|
|
||||||
|
if (time == "Sat 0300") nameOut = name.replace("Open Mic - Anyone can stream", "Synth Battle Royale");
|
||||||
|
|
||||||
|
parsedLines.push({
|
||||||
|
timestamp,
|
||||||
|
id,
|
||||||
|
name: nameOut.replace(" <- Up NEXT", ""),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let liveNow = {name: "ident", id: "aNONradio"};
|
||||||
|
|
||||||
|
if (parsedLines[0].name.includes("<- Live NOW")) {
|
||||||
|
liveNow = parsedLines.splice(0, 1)[0];
|
||||||
|
liveNow.name = liveNow.name.replace(" <- Live NOW", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = "";
|
||||||
|
let subtitle = "";
|
||||||
|
|
||||||
|
if (playing.includes("listeners with a daily peak of")) {
|
||||||
|
title = `${liveNow.name} (\`${liveNow.id}\`)`;
|
||||||
|
subtitle = playing;
|
||||||
|
} else {
|
||||||
|
const [_, current, peakDay, peakMonth, dj, metadata] = playing.match(/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/);
|
||||||
|
|
||||||
|
if (metadata == "https://archives.anonradio.net" || liveNow.name == "Synth Battle Royale") {
|
||||||
|
title = `${liveNow.name} (\`${dj}\`)`;
|
||||||
|
} else {
|
||||||
|
title = `${metadata} (\`${dj}\`)`;
|
||||||
|
}
|
||||||
|
subtitle = `${current} listening with a daily peak of ${peakDay} and ${peakMonth} peak for the month.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let openmicTime = "";
|
||||||
|
if (liveNow.id == "openmic") {
|
||||||
|
const streamData = icecast.icestats.source.find((src) => src.listenurl == "http://anonradio.net:8010/openmic");
|
||||||
|
if (streamData && streamData.stream_start_iso8601) {
|
||||||
|
const startTime = new Date(streamData.stream_start_iso8601).getTime();
|
||||||
|
openmicTime = `-\\*- OpenMIC DJ has been streaming for ${formatTime(Date.now() - startTime)} -\\*-\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: subtitle,
|
||||||
|
author: {
|
||||||
|
name: title,
|
||||||
|
url: "http://anonradio.net:8000/anonradio",
|
||||||
|
},
|
||||||
|
description: openmicTime + "__Schedule:__",
|
||||||
|
fields: parsedLines.map((line) => ({
|
||||||
|
inline: true,
|
||||||
|
name: `${line.name} (\`${line.id}\`)`,
|
||||||
|
value: `<t:${line.timestamp}:R>`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(anonradio);
|
167
src/modules/misc/color.js
Normal file
167
src/modules/misc/color.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
const InteractionCommand = require("../lib/interactionCommand.js");
|
||||||
|
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
|
||||||
|
|
||||||
|
const {safeString} = require("../util/misc.js");
|
||||||
|
|
||||||
|
const {tinycolor, random: randomColor} = require("@ctrl/tinycolor");
|
||||||
|
const sharp = require("sharp");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const {resolve} = require("node:path");
|
||||||
|
|
||||||
|
const colornamesRaw = fs.readFileSync(resolve(__dirname, "../../data/colornames.csv"), "utf8");
|
||||||
|
|
||||||
|
const color = new Command("color");
|
||||||
|
color.category = "misc";
|
||||||
|
color.helpText = "Show information on a color or get a random color";
|
||||||
|
color.callback = async function (msg, line, args, {truerandom, first}) {
|
||||||
|
const query = args.join(" ");
|
||||||
|
let color = tinycolor(query),
|
||||||
|
random = false;
|
||||||
|
|
||||||
|
if (!query || query == "") {
|
||||||
|
color = truerandom ? tinycolor(Math.floor(Math.random() * 0xffffff)) : randomColor();
|
||||||
|
random = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!color.isValid) {
|
||||||
|
const search = `^([0-9a-f]{6}),${query},`;
|
||||||
|
if (!first) {
|
||||||
|
const colornamesMatches = colornamesRaw.match(new RegExp(search, "img"));
|
||||||
|
if (colornamesMatches.length > 1) {
|
||||||
|
const hexes = colornamesMatches.map((m) => m.split(",")[0]);
|
||||||
|
const random = hexes[Math.floor(Math.random() * hexes.length)];
|
||||||
|
|
||||||
|
const left = "- #" + hexes.slice(0, Math.ceil(hexes.length / 2)).join("\n- #");
|
||||||
|
const right = "- #" + hexes.slice(Math.ceil(hexes.length / 2), hexes.length).join("\n- #");
|
||||||
|
|
||||||
|
return {
|
||||||
|
embed: {
|
||||||
|
color: parseInt(`0x${random}`),
|
||||||
|
footer: {
|
||||||
|
text: `Picked #${random} for embed color \u2022 Use \`--first\` or the \`first\` option to bypass this`,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{name: `Got ${colornamesMatches.length} matches for \`${safeString(query)}\``, value: left, inline: true},
|
||||||
|
{name: "\u200b", value: right, inline: true},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const colornamesHex = colornamesRaw.match(new RegExp(search, "im"))?.[1];
|
||||||
|
if (colornamesHex) {
|
||||||
|
color = tinycolor(colornamesHex);
|
||||||
|
} else {
|
||||||
|
return "Color not valid.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
channels: 3,
|
||||||
|
background: {r: color.r, g: color.g, b: color.b},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
const hex = color.toHex();
|
||||||
|
const fileName = `${hex}.png`;
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: "Hex",
|
||||||
|
value: color.toHexString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RGB",
|
||||||
|
value: color.toRgbString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HSL",
|
||||||
|
value: color.toHslString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HSV",
|
||||||
|
value: color.toHsvString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Integer",
|
||||||
|
value: color.toNumber(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (color.toName() != false) {
|
||||||
|
fields.splice(0, 0, {
|
||||||
|
name: "Name",
|
||||||
|
value: color.toName(),
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const colornamesName = colornamesRaw.match(new RegExp(`^${hex},([^,]+?),`, "im"))?.[1];
|
||||||
|
if (colornamesName) {
|
||||||
|
fields.push({
|
||||||
|
name: "\u200b",
|
||||||
|
value: `**[colornames](https://colornames.org/color/${hex}) Name**\n${colornamesName}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: random ? "Random Color" : "",
|
||||||
|
color: color.toNumber(),
|
||||||
|
thumbnail: {
|
||||||
|
url: `attachment://${fileName}`,
|
||||||
|
},
|
||||||
|
fields,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
file: {
|
||||||
|
file: image,
|
||||||
|
name: fileName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(color);
|
||||||
|
|
||||||
|
const colorInteraction = new InteractionCommand("color");
|
||||||
|
colorInteraction.helpText = "Show information on a color or get a random color";
|
||||||
|
colorInteraction.options.input = {
|
||||||
|
name: "input",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "Color to get info on",
|
||||||
|
required: false,
|
||||||
|
default: "",
|
||||||
|
};
|
||||||
|
colorInteraction.options.truerandom = {
|
||||||
|
name: "truerandom",
|
||||||
|
type: ApplicationCommandOptionTypes.BOOLEAN,
|
||||||
|
description: "Should the random color give a 'true random' color instead of an adjust color",
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
};
|
||||||
|
colorInteraction.options.first = {
|
||||||
|
name: "first",
|
||||||
|
type: ApplicationCommandOptionTypes.BOOLEAN,
|
||||||
|
description: "Take first match for a colornames color by name",
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
};
|
||||||
|
colorInteraction.callback = async function (interaction) {
|
||||||
|
const input = this.getOption(interaction, "input");
|
||||||
|
const truerandom = this.getOption(interaction, "truerandom");
|
||||||
|
const first = this.getOption(interaction, "first");
|
||||||
|
|
||||||
|
return color.callback(interaction, input, [input], {truerandom, first});
|
||||||
|
};
|
||||||
|
hf.registerCommand(colorInteraction);
|
62
src/modules/misc/generate.js
Normal file
62
src/modules/misc/generate.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
|
||||||
|
const {safeString} = require("../util/misc.js");
|
||||||
|
const {formatTime} = require("../util/time.js");
|
||||||
|
|
||||||
|
const GENERATE_HEADERS = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
const generate = new Command("generate");
|
||||||
|
generate.category = "misc";
|
||||||
|
generate.helpText = "Generate images from prompt via craiyon";
|
||||||
|
generate.callback = async function (msg, line) {
|
||||||
|
if (!line || line.length === 0) return "Arguments required.";
|
||||||
|
msg.channel.sendTyping();
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
let request = await fetch("https://backend.craiyon.com/generate", {
|
||||||
|
method: "POST",
|
||||||
|
headers: GENERATE_HEADERS,
|
||||||
|
body: JSON.stringify({prompt: line}),
|
||||||
|
});
|
||||||
|
while (request.status !== 200) {
|
||||||
|
request = await fetch("https://backend.craiyon.com/generate", {
|
||||||
|
method: "POST",
|
||||||
|
headers: GENERATE_HEADERS,
|
||||||
|
body: JSON.stringify({prompt: line}),
|
||||||
|
});
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await request.json();
|
||||||
|
const images = data.images
|
||||||
|
.map((img) => Buffer.from(img, "base64"))
|
||||||
|
.map((img, index) => ({contents: img, name: `${index}.jpg`}));
|
||||||
|
|
||||||
|
const title = `Responses for "${safeString(line)}"`;
|
||||||
|
|
||||||
|
const out = {
|
||||||
|
content: `Generated in ${formatTime(Date.now() - start)}${retries > 0 ? " with " + retries + " retries" : ""}`,
|
||||||
|
embeds: [],
|
||||||
|
files: images,
|
||||||
|
};
|
||||||
|
|
||||||
|
let splitIndex = 0;
|
||||||
|
for (const index in images) {
|
||||||
|
if (index % 3 == 0) splitIndex++;
|
||||||
|
out.embeds.push({
|
||||||
|
title,
|
||||||
|
url: "https://www.craiyon.com/?" + splitIndex,
|
||||||
|
image: {
|
||||||
|
url: `attachment://${index}.jpg`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
hf.registerCommand(generate);
|
64
src/modules/misc/gimg.js
Normal file
64
src/modules/misc/gimg.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
|
||||||
|
const GoogleImages = require("google-images");
|
||||||
|
|
||||||
|
const imagesClient = new GoogleImages(hf.apikeys.gimg, hf.apikeys.google);
|
||||||
|
|
||||||
|
const gimg = new Command("gimg");
|
||||||
|
gimg.category = "misc";
|
||||||
|
gimg.helpText = "Search Google Images";
|
||||||
|
gimg.usage = "[query]";
|
||||||
|
gimg.addAlias("img");
|
||||||
|
gimg.callback = async function (msg, line) {
|
||||||
|
if (!line) return "No arguments given.";
|
||||||
|
|
||||||
|
const images = await imagesClient.search(line, {
|
||||||
|
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
|
||||||
|
});
|
||||||
|
|
||||||
|
const index = Math.floor(Math.random() * images.length);
|
||||||
|
const image = images[index];
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: image.description,
|
||||||
|
url: image.parentPage,
|
||||||
|
image: {
|
||||||
|
url: image.url,
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
text: `Image ${Number(index) + 1}/${images.length}. Rerun to get a different image.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(gimg);
|
||||||
|
|
||||||
|
const fimg = new Command("fimg");
|
||||||
|
fimg.category = "misc";
|
||||||
|
fimg.helpText = "Send first result from Google Images";
|
||||||
|
fimg.usage = "[query]";
|
||||||
|
fimg.callback = async function (msg, line) {
|
||||||
|
if (!line) return "No arguments given.";
|
||||||
|
|
||||||
|
const images = await imagesClient.search(line, {
|
||||||
|
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
|
||||||
|
});
|
||||||
|
|
||||||
|
const image = images[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: image.description,
|
||||||
|
url: image.parentPage,
|
||||||
|
image: {
|
||||||
|
url: image.url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(fimg);
|
254
src/modules/misc/mcserver.js
Normal file
254
src/modules/misc/mcserver.js
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
const logger = require("../lib/logger.js");
|
||||||
|
|
||||||
|
const net = require("node:net");
|
||||||
|
const {resolveSrv} = require("node:dns/promises");
|
||||||
|
|
||||||
|
function readVarInt(data) {
|
||||||
|
var result = 0;
|
||||||
|
var offset = 0;
|
||||||
|
var pos = 0;
|
||||||
|
for (; offset < 32; offset += 7) {
|
||||||
|
var b = data[pos];
|
||||||
|
if (b == -1) {
|
||||||
|
throw new Error("Malformed varint");
|
||||||
|
}
|
||||||
|
result |= (b & 0x7f) << offset;
|
||||||
|
if ((b & 0x80) == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
// Keep reading up to 64 bits.
|
||||||
|
for (; offset < 64; offset += 7) {
|
||||||
|
b = data[pos];
|
||||||
|
if (b == -1) {
|
||||||
|
throw new Error("Truncated message");
|
||||||
|
}
|
||||||
|
if ((b & 0x80) == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVarInt(value) {
|
||||||
|
var buf = [];
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
if ((value & ~0x7f) === 0) {
|
||||||
|
buf.push(value);
|
||||||
|
return Buffer.from(buf);
|
||||||
|
} else {
|
||||||
|
buf.push((value & 0x7f) | 0x80);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ident = Buffer.from("HiddenPhox (c7.pm) ", "utf8");
|
||||||
|
const identPort = Buffer.alloc(2);
|
||||||
|
identPort.writeUInt16BE(3);
|
||||||
|
|
||||||
|
const handshake = Buffer.concat([
|
||||||
|
writeVarInt(0x0),
|
||||||
|
writeVarInt(1073741953),
|
||||||
|
writeVarInt(ident.length),
|
||||||
|
ident,
|
||||||
|
identPort,
|
||||||
|
writeVarInt(1),
|
||||||
|
]);
|
||||||
|
const handshakeWithLength = Buffer.concat([writeVarInt(handshake.length), handshake]);
|
||||||
|
|
||||||
|
const status = Buffer.concat([writeVarInt(1), writeVarInt(0x0)]);
|
||||||
|
|
||||||
|
const HANDSHAKE_PACKET = Buffer.concat([handshakeWithLength, status]);
|
||||||
|
|
||||||
|
const formattingToAnsi = {
|
||||||
|
r: "0",
|
||||||
|
l: "1",
|
||||||
|
m: "9",
|
||||||
|
n: "4",
|
||||||
|
o: "3",
|
||||||
|
0: "30",
|
||||||
|
1: "34",
|
||||||
|
2: "32",
|
||||||
|
3: "36",
|
||||||
|
4: "31",
|
||||||
|
5: "35",
|
||||||
|
6: "33",
|
||||||
|
7: "37",
|
||||||
|
8: "90",
|
||||||
|
9: "94",
|
||||||
|
a: "92",
|
||||||
|
b: "96",
|
||||||
|
c: "91",
|
||||||
|
d: "95",
|
||||||
|
e: "93",
|
||||||
|
f: "97",
|
||||||
|
};
|
||||||
|
|
||||||
|
function queryServer(ip, port, HANDSHAKE_PACKET) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logger.verbose("mcserver", "querying", ip, port);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = net.createConnection(
|
||||||
|
{
|
||||||
|
host: ip,
|
||||||
|
port: port,
|
||||||
|
timeout: 60000,
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
logger.verbose("mcserver", "connect");
|
||||||
|
client.write(HANDSHAKE_PACKET);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
logger.verbose("mcserver", "timeout");
|
||||||
|
client.destroy();
|
||||||
|
resolve("timeout");
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
let totalData = Buffer.alloc(0);
|
||||||
|
let length = 0;
|
||||||
|
client.on("data", function (data) {
|
||||||
|
if (length == 0) {
|
||||||
|
length = readVarInt(data);
|
||||||
|
logger.verbose("mcserver", "expected data length", length);
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
totalData = Buffer.concat([totalData, data]);
|
||||||
|
if (totalData.length >= length) {
|
||||||
|
client.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.verbose("mcserver", "data", data.length, totalData.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("close", function (err) {
|
||||||
|
if (err) {
|
||||||
|
logger.verbose("mcserver", "close with error", err);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const dataAsString = totalData.toString().trim();
|
||||||
|
console.log(dataAsString);
|
||||||
|
const json = JSON.parse(dataAsString.slice(dataAsString.indexOf("{"), dataAsString.lastIndexOf("}") + 1));
|
||||||
|
logger.verbose("mcserver", "close", json);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
return resolve(json);
|
||||||
|
});
|
||||||
|
client.on("timeout", function () {});
|
||||||
|
} catch (err) {
|
||||||
|
logger.verbose("mcserver", "failed:", err);
|
||||||
|
resolve({error: err});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function queryServerIdent(ip, port) {
|
||||||
|
return await queryServer(ip, port, HANDSHAKE_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function queryServerNoIdent(ip, port) {
|
||||||
|
const ident = Buffer.from(ip, "utf8");
|
||||||
|
const identPort = Buffer.alloc(2);
|
||||||
|
identPort.writeUInt16BE(port);
|
||||||
|
|
||||||
|
const handshake = Buffer.concat([
|
||||||
|
writeVarInt(0x0),
|
||||||
|
writeVarInt(1073741953),
|
||||||
|
writeVarInt(ident.length),
|
||||||
|
ident,
|
||||||
|
identPort,
|
||||||
|
writeVarInt(1),
|
||||||
|
]);
|
||||||
|
const handshakeWithLength = Buffer.concat([writeVarInt(handshake.length), handshake]);
|
||||||
|
|
||||||
|
const status = Buffer.concat([writeVarInt(1), writeVarInt(0x0)]);
|
||||||
|
|
||||||
|
const HANDSHAKE_PACKET = Buffer.concat([handshakeWithLength, status]);
|
||||||
|
|
||||||
|
return await queryServer(ip, port, HANDSHAKE_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mcserver = new Command("mcserver");
|
||||||
|
mcserver.category = "misc";
|
||||||
|
mcserver.helpText = "Query a Minecraft server";
|
||||||
|
mcserver.callback = async function (msg, line) {
|
||||||
|
if (!line || line == "") return "Arguments required.";
|
||||||
|
|
||||||
|
const split = line.split(":");
|
||||||
|
let ip = split[0];
|
||||||
|
const port = split[1] ?? 25565;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const addrs = await resolveSrv(ip);
|
||||||
|
if (addrs.length > 0) {
|
||||||
|
const mcAddr = addrs.find((a) => a.port == port);
|
||||||
|
if (mcAddr?.name) ip = mcAddr.name;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
await msg.addReaction("\uD83C\uDFD3");
|
||||||
|
|
||||||
|
let data = await queryServerIdent(ip, port);
|
||||||
|
|
||||||
|
if (data == "timeout") {
|
||||||
|
await msg.removeReaction("\uD83C\uDFD3");
|
||||||
|
return "Timed out trying to query.";
|
||||||
|
} else if (data?.error) {
|
||||||
|
await msg.removeReaction("\uD83C\uDFD3");
|
||||||
|
return `Failed to query:\n\`\`\`\n${data.error}\n\`\`\``;
|
||||||
|
} else {
|
||||||
|
if (data.version.name == "TCPShield.com") data = await queryServerNoIdent(ip, port);
|
||||||
|
|
||||||
|
await msg.removeReaction("\uD83C\uDFD3");
|
||||||
|
|
||||||
|
const motd = data.description.text.replace(/\u00a7([a-f0-9k-or])/gi, (formatting) => {
|
||||||
|
const ansi = formattingToAnsi[formatting];
|
||||||
|
return ansi ? `\x1b[${ansi}m` : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const players = data.players?.sample?.map((player) => player.name) ?? [];
|
||||||
|
const totalPlayers = `(${data.players.online}/${data.players.max})`;
|
||||||
|
let image;
|
||||||
|
if (data.favicon) {
|
||||||
|
image = Buffer.from(data.favicon.slice(data.favicon.indexOf(",")), "base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
embed: {
|
||||||
|
title: `Server info for: \`${line}\``,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "MOTD",
|
||||||
|
value: `\`\`\`ansi\n${motd}\n\`\`\``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Version",
|
||||||
|
value: `${data.version.name} (\`${data.version.protocol}\`)`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `Players ${players.length > 0 ? totalPlayers : ""}`,
|
||||||
|
value: players.length > 0 ? players.join(", ") : totalPlayers,
|
||||||
|
inline: players.length == 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
thumbnail: image && {
|
||||||
|
url: "attachment://icon.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
file: image && {
|
||||||
|
file: image,
|
||||||
|
name: "icon.png",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
hf.registerCommand(mcserver);
|
50
src/modules/misc/poll.js
Normal file
50
src/modules/misc/poll.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
|
||||||
|
const {formatUsername} = require("../util/misc.js");
|
||||||
|
|
||||||
|
const poll = new Command("poll");
|
||||||
|
poll.category = "misc";
|
||||||
|
poll.helpText = "Start a poll";
|
||||||
|
poll.usage = "[topic] [option 1] [option 2] [...option 3-10]";
|
||||||
|
poll.callback = async function (msg, line, [topic, ...options]) {
|
||||||
|
if (!line || !topic) return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"';
|
||||||
|
|
||||||
|
const arrOptions = [...options].slice(0, 10);
|
||||||
|
if (arrOptions.length < 2) return "A minimum of two options are required.";
|
||||||
|
|
||||||
|
const reactions = [];
|
||||||
|
let displayString = `**${formatUsername(msg.author)}** has started a poll:\n## __${topic}__\n`;
|
||||||
|
for (let i = 0; i < arrOptions.length; i++) {
|
||||||
|
displayString += (i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`) + ": " + arrOptions[i] + "\n";
|
||||||
|
reactions[i] = i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: displayString,
|
||||||
|
addReactions: reactions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(poll);
|
||||||
|
|
||||||
|
const vote = new Command("vote");
|
||||||
|
vote.category = "misc";
|
||||||
|
vote.helpText = "Start a yes/no vote";
|
||||||
|
vote.usage = "[topic]";
|
||||||
|
vote.callback = async function (msg, line, topic, {maybe}) {
|
||||||
|
if (!topic) return "No topic given.";
|
||||||
|
topic = topic.join(" ");
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: `**${formatUsername(
|
||||||
|
msg.author
|
||||||
|
)}** has started a vote:\n## __${topic}__\n<:ms_tick:503341995348066313>: Yes\n<:ms_cross:503341994974773250>: No${
|
||||||
|
maybe ? "\n<:ms_tilda:581268710925271095>: Maybe/Uncertain" : ""
|
||||||
|
}`,
|
||||||
|
addReactions: [
|
||||||
|
":ms_tick:503341995348066313",
|
||||||
|
":ms_cross:503341994974773250",
|
||||||
|
maybe && ":ms_tilda:581268710925271095",
|
||||||
|
].filter((x) => x != null),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(vote);
|
85
src/modules/misc/search.js
Normal file
85
src/modules/misc/search.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
const InteractionCommand = require("../lib/interactionCommand.js");
|
||||||
|
|
||||||
|
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
|
||||||
|
|
||||||
|
const {safeString} = require("../util/misc.js");
|
||||||
|
const {parseHtmlEntities} = require("../util/html.js");
|
||||||
|
|
||||||
|
const search = new Command("search");
|
||||||
|
search.category = "misc";
|
||||||
|
search.helpText = "Search, powered by LibreX";
|
||||||
|
search.addAlias("g");
|
||||||
|
search.addAlias("google");
|
||||||
|
search.addAlias("ddg");
|
||||||
|
search.callback = async function (msg, line, args, {results = 2}) {
|
||||||
|
const query = args.join(" ");
|
||||||
|
if (!hf.config.librex) return "LibreX instance not defined.";
|
||||||
|
if (!query || query == "") return "Search query required.";
|
||||||
|
|
||||||
|
const encodedQuery = encodeURIComponent(query);
|
||||||
|
|
||||||
|
if (query.startsWith("!")) {
|
||||||
|
const url = `https://api.duckduckgo.com/?q=${encodedQuery}&format=json`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (res.url != url) return res.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${hf.config.librex}/api.php?q=${encodedQuery}&p=0&t=0`).then((res) => res.json());
|
||||||
|
if (res.error?.message) {
|
||||||
|
if (res.error.message.indexOf("No results found.") > -1) {
|
||||||
|
return "Search returned no results.";
|
||||||
|
} else {
|
||||||
|
return `Search returned error:\n\`\`\`\n${res.error.message}\`\`\``;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const searchResults = Object.values(res)
|
||||||
|
.filter((result) => result.did_you_mean == null && typeof result !== "string")
|
||||||
|
.splice(0, Number(results));
|
||||||
|
|
||||||
|
if (searchResults.length > 0) {
|
||||||
|
let out = `__**Results for \`${safeString(query)}\`**__\n`;
|
||||||
|
for (const result of searchResults) {
|
||||||
|
if (result.special_response) {
|
||||||
|
out += "> " + safeString(parseHtmlEntities(result.special_response.response.split("\n").join("\n> ")));
|
||||||
|
out += `\n<${encodeURI(result.special_response.source)}>`;
|
||||||
|
} else {
|
||||||
|
out += `**${safeString(parseHtmlEntities(result.title)).trim()}** - <${encodeURI(result.url)}>`;
|
||||||
|
out += `\n> ${safeString(parseHtmlEntities(result.description))}`;
|
||||||
|
}
|
||||||
|
out += "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.trim();
|
||||||
|
} else {
|
||||||
|
return "Search returned no results.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
hf.registerCommand(search);
|
||||||
|
|
||||||
|
const searchInteraction = new InteractionCommand("search");
|
||||||
|
searchInteraction.helpText = "Search, powered by LibreX";
|
||||||
|
searchInteraction.options.query = {
|
||||||
|
name: "query",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "What to search for",
|
||||||
|
required: true,
|
||||||
|
default: "",
|
||||||
|
};
|
||||||
|
searchInteraction.options.results = {
|
||||||
|
name: "results",
|
||||||
|
type: ApplicationCommandOptionTypes.INTEGER,
|
||||||
|
description: "How many results to show",
|
||||||
|
required: false,
|
||||||
|
min_value: 1,
|
||||||
|
max_value: 10,
|
||||||
|
default: 2,
|
||||||
|
};
|
||||||
|
searchInteraction.callback = async function (interaction) {
|
||||||
|
const query = this.getOption(interaction, "query");
|
||||||
|
const results = this.getOption(interaction, "results");
|
||||||
|
|
||||||
|
return search.callback(interaction, query, [query], {results});
|
||||||
|
};
|
||||||
|
hf.registerCommand(searchInteraction);
|
49
src/modules/misc/shodan.js
Normal file
49
src/modules/misc/shodan.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
|
||||||
|
const REGEX_IPV4 = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
||||||
|
|
||||||
|
const shodan = new Command("shodan");
|
||||||
|
shodan.category = "misc";
|
||||||
|
shodan.helpText = "Look up an IP on Shodan InternetDB";
|
||||||
|
shodan.callback = async function (msg, line) {
|
||||||
|
if (!line || line == "") return "Arguments required.";
|
||||||
|
if (!REGEX_IPV4.test(line)) return "Invalid IP address.";
|
||||||
|
|
||||||
|
const data = await fetch("https://internetdb.shodan.io/" + line).then((res) => res.json());
|
||||||
|
|
||||||
|
if (data.detail) return data.detail;
|
||||||
|
|
||||||
|
return {
|
||||||
|
embed: {
|
||||||
|
title: `Results for \`${data.ip}\``,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Hostnames",
|
||||||
|
value: data.hostnames.length > 0 ? data.hostnames.map((str) => `\`${str}\``).join(" ") : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Open ports",
|
||||||
|
value: data.ports.length > 0 ? data.ports.join(", ") : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tags",
|
||||||
|
value: data.tags.length > 0 ? data.tags.map((str) => `\`${str}\``).join(", ") : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CPEs",
|
||||||
|
value: data.cpes.length > 0 ? data.cpes.map((str) => `\`${str}\``).join(" ") : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vulnerabilities",
|
||||||
|
value: data.vulns.length > 0 ? data.vulns.map((str) => `\`${str}\``).join(" ") : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hf.registerCommand(shodan);
|
118
src/modules/misc/wolfram.js
Normal file
118
src/modules/misc/wolfram.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
const InteractionCommand = require("../lib/interactionCommand.js");
|
||||||
|
|
||||||
|
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
|
||||||
|
|
||||||
|
const {safeString} = require("../util/misc.js");
|
||||||
|
|
||||||
|
const WA_NO_ANSWER = "<:ms_cross:503341994974773250> No answer.";
|
||||||
|
|
||||||
|
const wolfram = new Command("wolfram");
|
||||||
|
wolfram.category = "misc";
|
||||||
|
wolfram.helpText = "Wolfram Alpha";
|
||||||
|
wolfram.usage = "<-v> [query]";
|
||||||
|
wolfram.addAlias("wa");
|
||||||
|
wolfram.addAlias("calc");
|
||||||
|
wolfram.callback = async function (msg, line, args, {verbose, v}) {
|
||||||
|
const _verbose = verbose ?? v;
|
||||||
|
const query = args.join(" ");
|
||||||
|
|
||||||
|
const req = await fetch(
|
||||||
|
`http://api.wolframalpha.com/v2/query?input=${encodeURIComponent(query)}&appid=LH2K8H-T3QKETAGT3&output=json`
|
||||||
|
).then((x) => x.json());
|
||||||
|
|
||||||
|
const data = req.queryresult.pods;
|
||||||
|
|
||||||
|
if (!data) return WA_NO_ANSWER;
|
||||||
|
|
||||||
|
// fake no answer
|
||||||
|
//if (data[0].subpods[0].plaintext.includes("geoIP")) return WA_NO_ANSWER;
|
||||||
|
|
||||||
|
if (_verbose) {
|
||||||
|
const embed = {
|
||||||
|
title: `Result for: \`${safeString(query)}\``,
|
||||||
|
fields: [],
|
||||||
|
footer: {
|
||||||
|
icon_url: "http://www.wolframalpha.com/share.png",
|
||||||
|
text: "Powered by Wolfram Alpha",
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
url: data[1].subpods[0].img.src,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const extra = data.slice(1, 6);
|
||||||
|
for (const x in extra) {
|
||||||
|
embed.fields.push({
|
||||||
|
name: extra[x].title,
|
||||||
|
value: `[${extra[x].subpods[0].plaintext.length > 0 ? extra[x].subpods[0].plaintext : "<click for image>"}](${
|
||||||
|
extra[x].subpods[0].img.src
|
||||||
|
})`,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {embed};
|
||||||
|
} else {
|
||||||
|
let image;
|
||||||
|
|
||||||
|
if (data[1].subpods[0].img.src)
|
||||||
|
try {
|
||||||
|
const res = await fetch(data[1].subpods[0].img.src);
|
||||||
|
if (res) {
|
||||||
|
const imgData = await res.arrayBuffer();
|
||||||
|
image = Buffer.from(imgData);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = "";
|
||||||
|
if (data[1].subpods[0].plaintext.length > 0) string = safeString(data[1].subpods[0].plaintext);
|
||||||
|
|
||||||
|
let text;
|
||||||
|
if (string.length > 2000 - (6 + safeString(query).length)) {
|
||||||
|
text = string;
|
||||||
|
string = "Output too long:";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: `\`${safeString(query)}\` -> ${string.length > 0 ? string : ""}`,
|
||||||
|
attachments: [
|
||||||
|
text && {
|
||||||
|
file: text,
|
||||||
|
filename: "message.txt",
|
||||||
|
},
|
||||||
|
image && {
|
||||||
|
file: image,
|
||||||
|
filename: "wolfram_output.gif",
|
||||||
|
},
|
||||||
|
].filter((x) => !!x),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
hf.registerCommand(wolfram);
|
||||||
|
|
||||||
|
const wolframInteraction = new InteractionCommand("wolfram");
|
||||||
|
wolframInteraction.helpText = "Wolfram Alpha";
|
||||||
|
wolframInteraction.options.query = {
|
||||||
|
name: "query",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "What to query Wolfram Alpha for",
|
||||||
|
required: true,
|
||||||
|
default: "",
|
||||||
|
};
|
||||||
|
wolframInteraction.options.verbose = {
|
||||||
|
name: "verbose",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "Verbose output",
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
};
|
||||||
|
wolframInteraction.callback = async function (interaction) {
|
||||||
|
const query = this.getOption(interaction, "query");
|
||||||
|
const verbose = this.getOption(interaction, "verbose");
|
||||||
|
|
||||||
|
return wolfram.callback(interaction, query, [query], {verbose});
|
||||||
|
};
|
||||||
|
hf.registerCommand(wolframInteraction);
|
89
src/modules/misc/yt.js
Normal file
89
src/modules/misc/yt.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
const Command = require("../lib/command.js");
|
||||||
|
const InteractionCommand = require("../lib/interactionCommand.js");
|
||||||
|
|
||||||
|
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
|
||||||
|
|
||||||
|
const {safeString} = require("../util/misc.js");
|
||||||
|
const {parseHtmlEntities} = require("../util/html.js");
|
||||||
|
|
||||||
|
const yt = new Command("youtube");
|
||||||
|
yt.addAlias("yt");
|
||||||
|
yt.category = "misc";
|
||||||
|
yt.helpText = "Search YouTube";
|
||||||
|
yt.usage = "[search term]";
|
||||||
|
yt.callback = async function (msg, line) {
|
||||||
|
if (!line) return "Arguments are required.";
|
||||||
|
|
||||||
|
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
|
||||||
|
x.json()
|
||||||
|
);
|
||||||
|
|
||||||
|
const topVid = req.items[0];
|
||||||
|
|
||||||
|
let out = `**${safeString(parseHtmlEntities(topVid.title))}** | \`${safeString(
|
||||||
|
parseHtmlEntities(topVid.uploaderName)
|
||||||
|
)}\`\nhttps://youtube.com${topVid.url}\n\n**__See Also:__**\n`;
|
||||||
|
|
||||||
|
for (let i = 1; i < 5; i++) {
|
||||||
|
const vid = req.items[i];
|
||||||
|
if (!vid) continue;
|
||||||
|
|
||||||
|
out += `- **${safeString(parseHtmlEntities(vid.title))}** | By: \`${safeString(
|
||||||
|
parseHtmlEntities(vid.uploaderName)
|
||||||
|
)}\` | <https://youtube.com${vid.url}>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
hf.registerCommand(yt);
|
||||||
|
|
||||||
|
const ytInteraction = new InteractionCommand("youtube");
|
||||||
|
ytInteraction.helpText = "Search Youtube";
|
||||||
|
ytInteraction.options.search = {
|
||||||
|
name: "search",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "Search query",
|
||||||
|
required: true,
|
||||||
|
default: "",
|
||||||
|
};
|
||||||
|
ytInteraction.callback = async function (interaction) {
|
||||||
|
const search = this.getOption(interaction, "search");
|
||||||
|
|
||||||
|
return yt.callback(interaction, search);
|
||||||
|
};
|
||||||
|
hf.registerCommand(ytInteraction);
|
||||||
|
|
||||||
|
const fyt = new Command("fyt");
|
||||||
|
fyt.category = "misc";
|
||||||
|
fyt.helpText = "Search YouTube and take the first result.";
|
||||||
|
fyt.usage = "[search term]";
|
||||||
|
fyt.callback = async function (msg, line) {
|
||||||
|
if (!line) return "Arguments are required.";
|
||||||
|
|
||||||
|
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
|
||||||
|
x.json()
|
||||||
|
);
|
||||||
|
|
||||||
|
const vid = req.items[0];
|
||||||
|
|
||||||
|
return `**${safeString(parseHtmlEntities(vid.title))}** | \`${safeString(
|
||||||
|
parseHtmlEntities(vid.uploaderName)
|
||||||
|
)}\`\nhttps://youtube.com${vid.url}`;
|
||||||
|
};
|
||||||
|
hf.registerCommand(fyt);
|
||||||
|
|
||||||
|
const fytInteraction = new InteractionCommand("fyt");
|
||||||
|
fytInteraction.helpText = "Search Youtube and take the first result.";
|
||||||
|
fytInteraction.options.search = {
|
||||||
|
name: "search",
|
||||||
|
type: ApplicationCommandOptionTypes.STRING,
|
||||||
|
description: "Search query",
|
||||||
|
required: true,
|
||||||
|
default: "",
|
||||||
|
};
|
||||||
|
fytInteraction.callback = async function (interaction) {
|
||||||
|
const search = this.getOption(interaction, "search");
|
||||||
|
|
||||||
|
return fyt.callback(interaction, search);
|
||||||
|
};
|
||||||
|
hf.registerCommand(fytInteraction);
|
Loading…
Reference in a new issue