diff --git a/.env.example b/.env.example index 582a421..184fed5 100644 --- a/.env.example +++ b/.env.example @@ -43,5 +43,12 @@ TMP_DOMAIN= # Port for serving metrics. Metrics served are compatible with Prometheus. METRICS= -# Set this to true if you want to use the external image API script, located in api/index.js -API=false +# The image API type to be used +# Set this to `none` to process all images locally +# Set this to `ws` if you want to use the external image API script, located in api/index.js +# Set this to `azure` to use the Azure Functions API +API_TYPE=none +# If API_TYPE is `azure`, set this to your Azure webhook URL +AZURE_URL= +# If API_TYPE is `azure`, set an optional password for webhook responses +AZURE_PASS= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 06b2fb9..4d28cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,130 @@ -cache/ -build/ -data/ -appold.js -migrate.js -config.json -.env +# Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data pids *.pid *.seed *.pid.lock -lib-cov -coverage -.nyc_output -.grunt -bower_components + +# node-waf configuration .lock-wscript -build/Release + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/ + +# Dependency directories node_modules/ jspm_packages/ -typings/ + +# Optional npm cache directory .npm + +# Optional eslint cache .eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history .node_repl_history + +# Output of 'npm pack' *.tgz + +# Yarn Integrity file .yarn-integrity -.next -.ftpconfig -coverage.lcov -bannedusers.json -*.code-workspace -todo.txt -.vscode/ -migratedb.js -processed.txt -data.sqlite + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress v2.x temp and cache directory +.temp .cache + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Debugging +*.heap +*.out.* + +# vscode stuff +.vscode +*.code-workspace + +# Databases +data/ +*.sqlite + +# Azure Functions artifacts +bin +obj +appsettings.json +local.settings.json + +# Azurite artifacts +__blobstorage__ +__queuestorage__ +__azurite_db*__.json \ No newline at end of file diff --git a/commands/general/info.js b/commands/general/info.js index 552d571..f2eb349 100644 --- a/commands/general/info.js +++ b/commands/general/info.js @@ -25,7 +25,7 @@ class InfoCommand extends Command { }, { name: "📝 Credits:", - value: "Bot by **[Essem](https://essem.space)** and **[various contributors](https://github.com/esmBot/esmBot/graphs/contributors)**\nIcon by **[MintBurrow](https://twitter.com/MintBurrow)**" + value: "Bot by **[Essem](https://essem.space)** and **[various contributors](https://github.com/esmBot/esmBot/graphs/contributors)**\nLogo by **[MintBurrow](https://twitter.com/MintBurrow)**" }, { name: "💬 Total Servers:", diff --git a/commands/image-editing/9gag.js b/commands/image-editing/9gag.js index 6aad4a2..356818e 100644 --- a/commands/image-editing/9gag.js +++ b/commands/image-editing/9gag.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class NineGagCommand extends ImageCommand { params = { - water: "./assets/images/9gag.png", + water: "assets/images/9gag.png", gravity: 6 }; diff --git a/commands/image-editing/avs4you.js b/commands/image-editing/avs4you.js index 3e4555c..bd8174f 100644 --- a/commands/image-editing/avs4you.js +++ b/commands/image-editing/avs4you.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class AVSCommand extends ImageCommand { params = { - water: "./assets/images/avs4you.png", + water: "assets/images/avs4you.png", gravity: 5, resize: true }; diff --git a/commands/image-editing/bandicam.js b/commands/image-editing/bandicam.js index 802c51e..19bb649 100644 --- a/commands/image-editing/bandicam.js +++ b/commands/image-editing/bandicam.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class BandicamCommand extends ImageCommand { params = { - water: "./assets/images/bandicam.png", + water: "assets/images/bandicam.png", gravity: 2, resize: true }; diff --git a/commands/image-editing/deviantart.js b/commands/image-editing/deviantart.js index 77d1e5e..44a3860 100644 --- a/commands/image-editing/deviantart.js +++ b/commands/image-editing/deviantart.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class DeviantArtCommand extends ImageCommand { params = { - water: "./assets/images/deviantart.png", + water: "assets/images/deviantart.png", gravity: 5, resize: true }; diff --git a/commands/image-editing/flag.js b/commands/image-editing/flag.js index ec0ce86..1032930 100644 --- a/commands/image-editing/flag.js +++ b/commands/image-editing/flag.js @@ -10,14 +10,14 @@ class FlagCommand extends ImageCommand { const text = this.type === "classic" ? this.args[0] : this.options.text; if (!text.match(emojiRegex)) return false; const flag = emoji.unemojify(text).replaceAll(":", "").replace("flag-", ""); - let path = `./assets/images/region-flags/png/${flag.toUpperCase()}.png`; - if (flag === "pirate_flag") path = "./assets/images/pirateflag.png"; - if (flag === "rainbow-flag") path = "./assets/images/rainbowflag.png"; - if (flag === "checkered_flag") path = "./assets/images/checkeredflag.png"; - if (flag === "transgender_flag") path = "./assets/images/transflag.png"; - if (text === "🏴󠁧󠁢󠁳󠁣󠁴󠁿") path = "./assets/images/region-flags/png/GB-SCT.png"; - if (text === "🏴󠁧󠁢󠁷󠁬󠁳󠁿") path = "./assets/images/region-flags/png/GB-WLS.png"; - if (text === "🏴󠁧󠁢󠁥󠁮󠁧󠁿") path = "./assets/images/region-flags/png/GB-ENG.png"; + let path = `assets/images/region-flags/png/${flag.toUpperCase()}.png`; + if (flag === "pirate_flag") path = "assets/images/pirateflag.png"; + if (flag === "rainbow-flag") path = "assets/images/rainbowflag.png"; + if (flag === "checkered_flag") path = "assets/images/checkeredflag.png"; + if (flag === "transgender_flag") path = "assets/images/transflag.png"; + if (text === "🏴󠁧󠁢󠁳󠁣󠁴󠁿") path = "assets/images/region-flags/png/GB-SCT.png"; + if (text === "🏴󠁧󠁢󠁷󠁬󠁳󠁿") path = "assets/images/region-flags/png/GB-WLS.png"; + if (text === "🏴󠁧󠁢󠁥󠁮󠁧󠁿") path = "assets/images/region-flags/png/GB-ENG.png"; try { await fs.promises.access(path); this.flagPath = path; diff --git a/commands/image-editing/funky.js b/commands/image-editing/funky.js index a7ebafc..2da7544 100644 --- a/commands/image-editing/funky.js +++ b/commands/image-editing/funky.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class FunkyCommand extends ImageCommand { params = { - water: "./assets/images/funky.png", + water: "assets/images/funky.png", gravity: 3, resize: true }; diff --git a/commands/image-editing/hypercam.js b/commands/image-editing/hypercam.js index 2183bc3..f053dde 100644 --- a/commands/image-editing/hypercam.js +++ b/commands/image-editing/hypercam.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class HypercamCommand extends ImageCommand { params = { - water: "./assets/images/hypercam.png", + water: "assets/images/hypercam.png", gravity: 1, resize: true }; diff --git a/commands/image-editing/ifunny.js b/commands/image-editing/ifunny.js index c5f96db..dee5a15 100644 --- a/commands/image-editing/ifunny.js +++ b/commands/image-editing/ifunny.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class iFunnyCommand extends ImageCommand { params = { - water: "./assets/images/ifunny.png", + water: "assets/images/ifunny.png", gravity: 8, resize: true, append: true diff --git a/commands/image-editing/kinemaster.js b/commands/image-editing/kinemaster.js index eb6b172..3543441 100644 --- a/commands/image-editing/kinemaster.js +++ b/commands/image-editing/kinemaster.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class KineMasterCommand extends ImageCommand { params = { - water: "./assets/images/kinemaster.png", + water: "assets/images/kinemaster.png", gravity: 3, resize: true }; diff --git a/commands/image-editing/memecenter.js b/commands/image-editing/memecenter.js index 2b81550..bbaf59c 100644 --- a/commands/image-editing/memecenter.js +++ b/commands/image-editing/memecenter.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class MemeCenterCommand extends ImageCommand { params = { - water: "./assets/images/memecenter.png", + water: "assets/images/memecenter.png", gravity: 9, mc: true }; diff --git a/commands/image-editing/shutterstock.js b/commands/image-editing/shutterstock.js index 1d80426..6c3e325 100644 --- a/commands/image-editing/shutterstock.js +++ b/commands/image-editing/shutterstock.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class ShutterstockCommand extends ImageCommand { params = { - water: "./assets/images/shutterstock.png", + water: "assets/images/shutterstock.png", gravity: 5, resize: true }; diff --git a/commands/image-editing/speechbubble.js b/commands/image-editing/speechbubble.js index 5835692..c76739c 100644 --- a/commands/image-editing/speechbubble.js +++ b/commands/image-editing/speechbubble.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class SpeechBubbleCommand extends ImageCommand { params = { - water: "./assets/images/speechbubble.png", + water: "assets/images/speechbubble.png", gravity: "north", resize: true, yscale: 0.2, diff --git a/natives/flag.cc b/natives/flag.cc index 6b444dd..8923aee 100644 --- a/natives/flag.cc +++ b/natives/flag.cc @@ -15,6 +15,7 @@ Napi::Value Flag(const Napi::CallbackInfo &info) { Napi::Buffer data = obj.Get("data").As>(); string overlay = obj.Get("overlay").As().Utf8Value(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -31,7 +32,8 @@ Napi::Value Flag(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - watermark.read(overlay); + string assetPath = basePath + overlay; + watermark.read(assetPath); watermark.alphaChannel(Magick::SetAlphaChannel); watermark.evaluate(Magick::AlphaChannel, Magick::MultiplyEvaluateOperator, 0.5); diff --git a/natives/gamexplain.cc b/natives/gamexplain.cc index 4524400..552830b 100644 --- a/natives/gamexplain.cc +++ b/natives/gamexplain.cc @@ -14,6 +14,7 @@ Napi::Value Gamexplain(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -30,7 +31,8 @@ Napi::Value Gamexplain(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - watermark.read("./assets/images/gamexplain.png"); + string assetPath = basePath + "assets/images/gamexplain.png"; + watermark.read(assetPath); coalesceImages(&coalesced, frames.begin(), frames.end()); for (Image &image : coalesced) { diff --git a/natives/globe.cc b/natives/globe.cc index 1da0c2b..1e5a1e1 100644 --- a/natives/globe.cc +++ b/natives/globe.cc @@ -14,6 +14,7 @@ Napi::Value Globe(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -31,8 +32,8 @@ Napi::Value Globe(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - distort.read("./assets/images/spheremap.png"); - overlay.read("./assets/images/sphere_overlay.png"); + distort.read(basePath + "assets/images/spheremap.png"); + overlay.read(basePath + "assets/images/sphere_overlay.png"); coalesceImages(&coalesced, frames.begin(), frames.end()); if (type != "gif") { diff --git a/natives/homebrew.cc b/natives/homebrew.cc index 08098bc..8f66827 100644 --- a/natives/homebrew.cc +++ b/natives/homebrew.cc @@ -12,11 +12,13 @@ Napi::Value Homebrew(const Napi::CallbackInfo &info) { try { Napi::Object obj = info[0].As(); string caption = obj.Get("caption").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); Blob blob; Image image; - image.read("./assets/images/hbc.png"); + string assetPath = basePath + "assets/images/hbc.png"; + image.read(assetPath); image.textGravity(Magick::CenterGravity); image.font("./assets/hbc.ttf"); image.textKerning(-5); diff --git a/natives/reddit.cc b/natives/reddit.cc index d36a761..4c2162d 100644 --- a/natives/reddit.cc +++ b/natives/reddit.cc @@ -14,6 +14,7 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) { Napi::Buffer data = obj.Get("data").As>(); string text = obj.Get("caption").As().Utf8Value(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -32,7 +33,7 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) { cerr << "Warning: " << warning.what() << endl; } - watermark.read("./assets/images/reddit.png"); + watermark.read(basePath + "assets/images/reddit.png"); text_image.textGravity(Magick::WestGravity); text_image.font("Roboto"); text_image.fontPointsize(47); diff --git a/natives/retro.cc b/natives/retro.cc index 2c4e259..f636b54 100644 --- a/natives/retro.cc +++ b/natives/retro.cc @@ -14,6 +14,7 @@ Napi::Value Retro(const Napi::CallbackInfo &info) { string line1 = obj.Get("line1").As().Utf8Value(); string line2 = obj.Get("line2").As().Utf8Value(); string line3 = obj.Get("line3").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); Blob blob; @@ -22,7 +23,7 @@ Napi::Value Retro(const Napi::CallbackInfo &info) { Image line2_text; Image line3_text; - image.read("./assets/images/retro.png"); + image.read(basePath + "assets/images/retro.png"); line2_text.backgroundColor("none"); line2_text.fontPointsize(128); diff --git a/natives/scott.cc b/natives/scott.cc index 1983ce8..59b5031 100644 --- a/natives/scott.cc +++ b/natives/scott.cc @@ -14,6 +14,7 @@ Napi::Value Scott(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -30,7 +31,7 @@ Napi::Value Scott(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - watermark.read("./assets/images/scott.png"); + watermark.read(basePath + "assets/images/scott.png"); coalesceImages(&coalesced, frames.begin(), frames.end()); for (Image &image : coalesced) { diff --git a/natives/sonic.cc b/natives/sonic.cc index a37cd0d..3700fe2 100644 --- a/natives/sonic.cc +++ b/natives/sonic.cc @@ -12,6 +12,7 @@ Napi::Value Sonic(const Napi::CallbackInfo &info) { try { Napi::Object obj = info[0].As(); string text = obj.Get("text").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); Blob blob; @@ -24,7 +25,7 @@ Napi::Value Sonic(const Napi::CallbackInfo &info) { text_image.read("pango:" + text + ""); text_image.resize(Geometry(474, 332)); text_image.extent(Geometry("1024x538-435-145"), Magick::CenterGravity); - image.read("./assets/images/sonic.jpg"); + image.read(basePath + "assets/images/sonic.jpg"); image.composite(text_image, Geometry("+160+10"), Magick::OverCompositeOp); image.magick("PNG"); image.write(&blob); diff --git a/natives/watermark.cc b/natives/watermark.cc index ebec903..ffcd679 100644 --- a/natives/watermark.cc +++ b/natives/watermark.cc @@ -26,6 +26,7 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) { ? obj.Get("append").As().Value() : false; bool mc = obj.Has("mc") ? obj.Get("mc").As().Value() : false; + string basePath = obj.Get("basePath").As().Utf8Value(); string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -43,7 +44,8 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - watermark.read(water); + string merged = basePath + water; + watermark.read(merged); if (resize && append) { string query(to_string(frames.front().baseColumns()) + "x"); watermark.scale(Geometry(query)); diff --git a/natives/wdt.cc b/natives/wdt.cc index 5b18317..699c6e7 100644 --- a/natives/wdt.cc +++ b/natives/wdt.cc @@ -14,6 +14,7 @@ Napi::Value Wdt(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -30,7 +31,7 @@ Napi::Value Wdt(const Napi::CallbackInfo &info) { } catch (Magick::Warning &warning) { cerr << "Warning: " << warning.what() << endl; } - watermark.read("./assets/images/whodidthis.png"); + watermark.read(basePath + "assets/images/whodidthis.png"); coalesceImages(&coalesced, frames.begin(), frames.end()); for (Image &image : coalesced) { diff --git a/natives/zamn.cc b/natives/zamn.cc index 9d88aee..2741088 100644 --- a/natives/zamn.cc +++ b/natives/zamn.cc @@ -13,6 +13,7 @@ Napi::Value Zamn(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); + string basePath = obj.Get("basePath").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; @@ -23,7 +24,7 @@ Napi::Value Zamn(const Napi::CallbackInfo &info) { list mid; Image watermark; readImages(&frames, Blob(data.Data(), data.Length())); - watermark.read("./assets/images/zamn.png"); + watermark.read(basePath + "assets/images/zamn.png"); coalesceImages(&coalesced, frames.begin(), frames.end()); for (Image &image : coalesced) { diff --git a/utils/image-runner.js b/utils/image-runner.js index a46116f..a5b4243 100644 --- a/utils/image-runner.js +++ b/utils/image-runner.js @@ -1,10 +1,13 @@ import { createRequire } from "module"; import { isMainThread, parentPort, workerData } from "worker_threads"; import fetch from "node-fetch"; +import path from "path"; +import { fileURLToPath } from "url"; const nodeRequire = createRequire(import.meta.url); -const magick = nodeRequire(`../build/${process.env.DEBUG && process.env.DEBUG === "true" ? "Debug" : "Release"}/image.node`); +const relPath = `../build/${process.env.DEBUG && process.env.DEBUG === "true" ? "Debug" : "Release"}/image.node`; +const magick = nodeRequire(relPath); const enumMap = { "forget": 0, @@ -42,6 +45,7 @@ export default function run(object) { objectWithFixedType.gravity = enumMap[objectWithFixedType.gravity]; } } + objectWithFixedType.basePath = path.join(path.dirname(fileURLToPath(import.meta.url)), "../"); try { const result = magick[object.cmd](objectWithFixedType); const returnObject = { diff --git a/utils/services/image.js b/utils/services/image.js index eec90ed..19829bf 100644 --- a/utils/services/image.js +++ b/utils/services/image.js @@ -5,6 +5,9 @@ import path from "path"; import { fileURLToPath } from "url"; import { Worker } from "worker_threads"; import { createRequire } from "module"; +import { createServer } from "http"; +import fetch from "node-fetch"; +import EventEmitter from "events"; // only requiring this to work around an issue regarding worker threads const nodeRequire = createRequire(import.meta.url); @@ -18,11 +21,14 @@ class ImageWorker extends BaseServiceWorker { console.info = (str) => this.ipc.sendToAdmiral("info", str); - if (process.env.API === "true") { - this.jobs = {}; + if (process.env.API_TYPE === "ws") { this.connections = new Map(); this.servers = JSON.parse(fs.readFileSync(new URL("../../servers.json", import.meta.url), { encoding: "utf8" })).image; this.nextID = 0; + } else if (process.env.API_TYPE === "azure") { + this.jobs = new Map(); + this.webhook = createServer(); + this.port = parseInt(process.env.WEBHOOK_PORT) || 3763; } this.begin().then(() => this.serviceReady()); @@ -30,7 +36,7 @@ class ImageWorker extends BaseServiceWorker { async begin() { // connect to image api if enabled - if (process.env.API === "true") { + if (process.env.API_TYPE === "ws") { for (const server of this.servers) { try { await this.connect(server.server, server.auth); @@ -38,6 +44,73 @@ class ImageWorker extends BaseServiceWorker { logger.error(e); } } + } else if (process.env.API_TYPE === "azure") { + this.webhook.on("request", async (req, res) => { + if (req.method !== "POST") { + res.statusCode = 405; + return res.end("405 Method Not Allowed"); + } + if (process.env.AZURE_PASS && req.headers.authorization !== process.env.AZURE_PASS) { + res.statusCode = 401; + return res.end("401 Unauthorized"); + } + const reqUrl = new URL(req.url, `http://${req.headers.host}`); + if (reqUrl.pathname === "/callback") { + try { + const chunks = []; + req.on("data", (data) => { + chunks.push(data); + }); + req.once("end", () => { + if (this.jobs.has(req.headers["x-azure-id"])) { + try { + const error = JSON.parse(Buffer.concat(chunks).toString()); + if (error.error) this.jobs.get(req.headers["x-azure-id"]).emit("error", new Error(error.message)); + } catch { + // no-op + } + const contentType = req.headers["content-type"]; + let type; + switch (contentType) { + case "image/gif": + type = "gif"; + break; + case "image/png": + type = "png"; + break; + case "image/jpeg": + type = "jpg"; + break; + case "image/webp": + type = "webp"; + break; + default: + type = contentType; + break; + } + this.jobs.get(req.headers["x-azure-id"]).emit("image", { buffer: Buffer.concat(chunks), type }); + return res.end("OK"); + } else { + res.statusCode = 409; + return res.end("409 Conflict"); + } + }); + } catch (e) { + logger.error("An error occurred while processing a webhook request: ", e); + res.statusCode = 500; + return res.end("500 Internal Server Error"); + } + } else { + res.statusCode = 404; + return res.end("404 Not Found"); + } + }); + this.webhook.on("error", (e) => { + logger.error("An error occurred on the Azure webhook: ", e); + }); + this.webhook.listen(this.port, () => { + logger.log(`Azure HTTP webhook listening on port ${this.port}`); + }); } } @@ -49,7 +122,7 @@ class ImageWorker extends BaseServiceWorker { async getRunning() { const statuses = []; - if (process.env.API === "true") { + if (process.env.API_TYPE === "ws") { for (const [address, connection] of this.connections) { if (connection.conn.readyState !== 0 && connection.conn.readyState !== 1) { continue; @@ -115,8 +188,17 @@ class ImageWorker extends BaseServiceWorker { }); } + waitForAzure(event) { + return new Promise((resolve, reject) => { + event.once("image", (data) => { + resolve(data); + }); + event.once("error", reject); + }); + } + async run(object) { - if (process.env.API === "true") { + if (process.env.API_TYPE === "ws") { let num = this.nextID++; if (num > 4294967295) num = this.nextID = 0; for (let i = 0; i < 3; i++) { @@ -135,6 +217,12 @@ class ImageWorker extends BaseServiceWorker { } } } + } else if (process.env.API_TYPE === "azure") { + object.callback = `${process.env.AZURE_CALLBACK_URL}:${this.port}/callback`; + const response = await fetch(`${process.env.AZURE_URL}/api/orchestrators/ImageOrchestrator`, { method: "POST", body: JSON.stringify(object) }).then(r => r.json()); + const event = new EventEmitter(); + this.jobs.set(response.id, event); + return await this.waitForAzure(event); } else { // Called from command (not using image API) const worker = new Worker(path.join(path.dirname(fileURLToPath(import.meta.url)), "../image-runner.js"), { diff --git a/utils/services/prometheus.js b/utils/services/prometheus.js index 867412f..c25d633 100644 --- a/utils/services/prometheus.js +++ b/utils/services/prometheus.js @@ -22,7 +22,7 @@ class PrometheusWorker extends BaseServiceWorker { # HELP esmbot_shard_count Number of shards the bot has # TYPE esmbot_shard_count gauge `); - if (process.env.API === "true") { + if (process.env.API_TYPE === "ws") { const servers = await this.ipc.serviceCommand("image", { type: "stats" }, true); res.write(`# HELP esmbot_connected_workers Number of workers connected # TYPE esmbot_connected_workers gauge