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 {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");
 | 
			
		||||
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;
 | 
			
		||||
for (const file of fs.readdirSync(resolve(__dirname, "misc"))) {
 | 
			
		||||
  try {
 | 
			
		||||
    playing = await fetch("https://anonradio.net/playing").then((res) => res.text());
 | 
			
		||||
    require(resolve(__dirname, "misc", file));
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    try {
 | 
			
		||||
      playing = await fetch("http://anonradio.net/playing").then((res) => res.text());
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      //
 | 
			
		||||
    }
 | 
			
		||||
    logger.error("hf:modules:misc", `Failed to load "${file}": ${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…
	
	Add table
		Add a link
		
	
		Reference in a new issue