diff options
| -rw-r--r-- | fig-frontend-client/Makefile | 23 | ||||
| -rw-r--r-- | fig-frontend-client/config-deploy.m4 | 2 | ||||
| -rw-r--r-- | fig-frontend-client/config-extension.js | 2 | ||||
| -rw-r--r-- | fig-frontend-client/config-test.m4 | 2 | ||||
| -rw-r--r-- | fig-frontend-client/extension/main.css | 0 | ||||
| -rw-r--r-- | fig-frontend-client/extension/manifest.dhall | 2 | ||||
| -rw-r--r-- | fig-frontend-client/main.css | 6 | ||||
| -rw-r--r-- | fig-frontend-client/main.js | 464 | ||||
| -rw-r--r-- | fig-frontend-client/src/Config.js | 1 | ||||
| -rw-r--r-- | fig-frontend-client/src/Config.purs | 1 | ||||
| -rw-r--r-- | fig-frontend-client/src/Main.purs | 71 | ||||
| -rw-r--r-- | fig-frontend-client/src/Model.js | 12 | ||||
| -rw-r--r-- | fig-frontend-client/src/UI.js | 2 | ||||
| -rw-r--r-- | fig-frontend-client/src/UI.purs | 13 | ||||
| -rw-r--r-- | fig-frontend-client/templates/index.html (renamed from fig-frontend-client/index-template.html) | 2 | ||||
| -rw-r--r-- | fig-frontend-client/templates/obs.html | 18 | ||||
| -rw-r--r-- | fig-monitor-twitch/main/Main.hs | 3 | ||||
| -rw-r--r-- | fig-monitor-twitch/src/Fig/Monitor/Twitch.hs | 20 | ||||
| -rw-r--r-- | fig-monitor-twitch/src/Fig/Monitor/Twitch/Utils.hs | 3 |
19 files changed, 536 insertions, 111 deletions
diff --git a/fig-frontend-client/Makefile b/fig-frontend-client/Makefile index 1f01f50..88295f0 100644 --- a/fig-frontend-client/Makefile +++ b/fig-frontend-client/Makefile @@ -1,8 +1,10 @@ -.PHONY: all deploy extension clean +.PHONY: all dist deploy extension clean -all: dist/test/index.html dist/test/assets dist/test/main.js dist/test/main.css +TEMPLATES=$(shell ls templates) -deploy: dist/deploy/index.html dist/deploy/assets dist/deploy/main.js dist/deploy/main.css +all: dist $(addprefix dist/test/,$(TEMPLATES)) dist/test/assets dist/test/main.js dist/test/main.css + +deploy: dist $(addprefix dist/deploy/,$(TEMPLATES)) dist/deploy/assets dist/deploy/main.js dist/deploy/main.css rsync -av dist/deploy/ "pub.colonq.computer:~/public_html/" dist: @@ -20,17 +22,30 @@ dist/%/assets: $(shell find assets) dist dist/%/main.js: main.js dist cp $< $@ +dist/extension/main.css: extension/main.css dist + cp $< $@ + dist/%/main.css: main.css dist cp $< $@ dist/%/index.html: config-%.m4 dist index-template.html sh -c "m4 $< >$@" -extension: dist/extension/assets dist/extension/manifest.json dist/extension/background.js dist/extension/main.js dist/extension/main.css + +define GEN_RULE +dist/%/$(template): config-%.m4 templates/$(template) + sh -c "m4 $$^ >$$@" +endef +$(foreach template,$(TEMPLATES), $(eval $(GEN_RULE))) + +extension: dist dist/extension/assets dist/extension/manifest.json dist/extension/background.js dist/extension/main.js dist/extension/main.css dist/extension/config.js dist/extension/manifest.json: extension/manifest.dhall dhall-to-json <$< >$@ +dist/extension/config.js: config-extension.js + cp $< $@ + dist/extension/%: extension/% cp $< $@ diff --git a/fig-frontend-client/config-deploy.m4 b/fig-frontend-client/config-deploy.m4 index 504bb3e..97fc61f 100644 --- a/fig-frontend-client/config-deploy.m4 +++ b/fig-frontend-client/config-deploy.m4 @@ -1,4 +1,4 @@ define(`CONFIG_SUBST', ` +globalThis.mode = 0; globalThis.apiServer = "https://api.colonq.computer/api"; ') -include(`index-template.html') diff --git a/fig-frontend-client/config-extension.js b/fig-frontend-client/config-extension.js new file mode 100644 index 0000000..44b69d3 --- /dev/null +++ b/fig-frontend-client/config-extension.js @@ -0,0 +1,2 @@ +globalThis.mode = 1; +globalThis.apiServer = "https://api.colonq.computer/api"; diff --git a/fig-frontend-client/config-test.m4 b/fig-frontend-client/config-test.m4 index f1e6c16..0b876e0 100644 --- a/fig-frontend-client/config-test.m4 +++ b/fig-frontend-client/config-test.m4 @@ -1,4 +1,4 @@ define(`CONFIG_SUBST', ` +globalThis.mode = 0; globalThis.apiServer = "http://localhost:8000/api"; ') -include(`index-template.html') diff --git a/fig-frontend-client/extension/main.css b/fig-frontend-client/extension/main.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/fig-frontend-client/extension/main.css diff --git a/fig-frontend-client/extension/manifest.dhall b/fig-frontend-client/extension/manifest.dhall index 87c48f6..cf9e7a1 100644 --- a/fig-frontend-client/extension/manifest.dhall +++ b/fig-frontend-client/extension/manifest.dhall @@ -22,7 +22,7 @@ } , content_scripts = [ { matches = ["*://*.twitch.tv/*"] - , js = ["main.js"] + , js = ["config.js", "main.js"] , css = ["main.css"] , run_at = "document_end" } diff --git a/fig-frontend-client/main.css b/fig-frontend-client/main.css index 3305e19..7714273 100644 --- a/fig-frontend-client/main.css +++ b/fig-frontend-client/main.css @@ -16,7 +16,7 @@ html, body { width: 100%; } -body { +body.lcolonq-index { font-family: "Iosevka Comfy"; font-weight: bold; color: black; @@ -29,6 +29,10 @@ body { linear-gradient(to bottom, grey 1px, transparent 1px); } +body.lcolonq-obs { + background-color: rbga(0,0,0,0); +} + #lcolonq-title { position: absolute; top: 2rem; diff --git a/fig-frontend-client/main.js b/fig-frontend-client/main.js index 67e726e..26c1be2 100644 --- a/fig-frontend-client/main.js +++ b/fig-frontend-client/main.js @@ -118,9 +118,9 @@ var $$void = function(dictFunctor) { return map(dictFunctor)($$const(unit)); }; var voidRight = function(dictFunctor) { - var map12 = map(dictFunctor); + var map13 = map(dictFunctor); return function(x) { - return map12($$const(x)); + return map13($$const(x)); }; }; var functorArray = { @@ -223,12 +223,32 @@ var EQ = /* @__PURE__ */ function() { return EQ2; }(); +// output/Data.Semigroup/foreign.js +var concatArray = function(xs) { + return function(ys) { + if (xs.length === 0) + return ys; + if (ys.length === 0) + return xs; + return xs.concat(ys); + }; +}; + // output/Data.Semigroup/index.js +var semigroupArray = { + append: concatArray +}; var append = function(dict) { return dict.append; }; // output/Data.Monoid/index.js +var monoidArray = { + mempty: [], + Semigroup0: function() { + return semigroupArray; + } +}; var mempty = function(dict) { return dict.mempty; }; @@ -296,15 +316,16 @@ var liftEffect = function(dict) { // output/Audio/index.js var playVoice = function(dictMonadEffect) { - var liftEffect5 = liftEffect(dictMonadEffect); + var liftEffect6 = liftEffect(dictMonadEffect); return function(b) { return function(i) { - return liftEffect5(_playVoice(b)(i)); + return liftEffect6(_playVoice(b)(i)); }; }; }; // output/Config/foreign.js +var mode = globalThis.mode; var apiServer = globalThis.apiServer; // output/Data.Array/foreign.js @@ -335,6 +356,9 @@ var replicatePolyfill = function(count, value12) { return result; }; var replicateImpl = typeof Array.prototype.fill === "function" ? replicateFill : replicatePolyfill; +var indexImpl = function(just, nothing, xs, i) { + return i < 0 || i >= xs.length ? nothing : just(xs[i]); +}; // output/Control.Alt/index.js var alt = function(dict) { @@ -635,6 +659,7 @@ var unwrap = function() { }; // output/Data.Foldable/index.js +var identity5 = /* @__PURE__ */ identity(categoryFn); var foldr = function(dict) { return dict.foldr; }; @@ -680,6 +705,15 @@ var foldableArray = { return foldMapDefaultR(foldableArray)(dictMonoid); } }; +var foldMap = function(dict) { + return dict.foldMap; +}; +var fold = function(dictFoldable) { + var foldMap2 = foldMap(dictFoldable); + return function(dictMonoid) { + return foldMap2(dictMonoid)(identity5); + }; +}; // output/Data.Function.Uncurried/foreign.js var runFn2 = function(fn) { @@ -698,9 +732,26 @@ var runFn3 = function(fn) { }; }; }; +var runFn4 = function(fn) { + return function(a) { + return function(b) { + return function(c) { + return function(d) { + return fn(a, b, c, d); + }; + }; + }; + }; +}; // output/Data.Array/index.js var range2 = /* @__PURE__ */ runFn2(rangeImpl); +var index = /* @__PURE__ */ function() { + return runFn4(indexImpl)(Just.create)(Nothing.value); +}(); +var head = function(xs) { + return index(xs)(0); +}; // output/Effect.Aff/foreign.js var Aff = function() { @@ -1215,7 +1266,7 @@ var Aff = function() { var root = EMPTY; function kill(error3, par2, cb2) { var step2 = par2; - var head = null; + var head2 = null; var tail = null; var count = 0; var kills2 = {}; @@ -1236,14 +1287,14 @@ var Aff = function() { }; }); } - if (head === null) { + if (head2 === null) { break loop; } - step2 = head._2; + step2 = head2._2; if (tail === null) { - head = null; + head2 = null; } else { - head = tail._1; + head2 = tail._1; tail = tail._2; } break; @@ -1252,10 +1303,10 @@ var Aff = function() { break; case APPLY: case ALT: - if (head) { - tail = new Aff2(CONS, head, tail); + if (head2) { + tail = new Aff2(CONS, head2, tail); } - head = step2; + head2 = step2; step2 = step2._1; break; } @@ -1271,7 +1322,7 @@ var Aff = function() { } return kills2; } - function join2(result, head, tail) { + function join2(result, head2, tail) { var fail2, step2, lhs, rhs, tmp, kid; if (util.isLeft(result)) { fail2 = result; @@ -1289,30 +1340,30 @@ var Aff = function() { if (interrupt !== null) { return; } - if (head === null) { + if (head2 === null) { cb(fail2 || step2)(); return; } - if (head._3 !== EMPTY) { + if (head2._3 !== EMPTY) { return; } - switch (head.tag) { + switch (head2.tag) { case MAP: if (fail2 === null) { - head._3 = util.right(head._1(util.fromRight(step2))); - step2 = head._3; + head2._3 = util.right(head2._1(util.fromRight(step2))); + step2 = head2._3; } else { - head._3 = fail2; + head2._3 = fail2; } break; case APPLY: - lhs = head._1._3; - rhs = head._2._3; + lhs = head2._1._3; + rhs = head2._2._3; if (fail2) { - head._3 = fail2; + head2._3 = fail2; tmp = true; kid = killId++; - kills[kid] = kill(early, fail2 === lhs ? head._2 : head._1, function() { + kills[kid] = kill(early, fail2 === lhs ? head2._2 : head2._1, function() { return function() { delete kills[kid]; if (tmp) { @@ -1332,24 +1383,24 @@ var Aff = function() { return; } else { step2 = util.right(util.fromRight(lhs)(util.fromRight(rhs))); - head._3 = step2; + head2._3 = step2; } break; case ALT: - lhs = head._1._3; - rhs = head._2._3; + lhs = head2._1._3; + rhs = head2._2._3; if (lhs === EMPTY && util.isLeft(rhs) || rhs === EMPTY && util.isLeft(lhs)) { return; } if (lhs !== EMPTY && util.isLeft(lhs) && rhs !== EMPTY && util.isLeft(rhs)) { fail2 = step2 === lhs ? rhs : lhs; step2 = null; - head._3 = fail2; + head2._3 = fail2; } else { - head._3 = step2; + head2._3 = step2; tmp = true; kid = killId++; - kills[kid] = kill(early, step2 === lhs ? head._2 : head._1, function() { + kills[kid] = kill(early, step2 === lhs ? head2._2 : head2._1, function() { return function() { delete kills[kid]; if (tmp) { @@ -1369,9 +1420,9 @@ var Aff = function() { break; } if (tail === null) { - head = null; + head2 = null; } else { - head = tail._1; + head2 = tail._1; tail = tail._2; } } @@ -1388,7 +1439,7 @@ var Aff = function() { function run3() { var status2 = CONTINUE; var step2 = par; - var head = null; + var head2 = null; var tail = null; var tmp, fid; loop: @@ -1399,31 +1450,31 @@ var Aff = function() { case CONTINUE: switch (step2.tag) { case MAP: - if (head) { - tail = new Aff2(CONS, head, tail); + if (head2) { + tail = new Aff2(CONS, head2, tail); } - head = new Aff2(MAP, step2._1, EMPTY, EMPTY); + head2 = new Aff2(MAP, step2._1, EMPTY, EMPTY); step2 = step2._2; break; case APPLY: - if (head) { - tail = new Aff2(CONS, head, tail); + if (head2) { + tail = new Aff2(CONS, head2, tail); } - head = new Aff2(APPLY, EMPTY, step2._2, EMPTY); + head2 = new Aff2(APPLY, EMPTY, step2._2, EMPTY); step2 = step2._1; break; case ALT: - if (head) { - tail = new Aff2(CONS, head, tail); + if (head2) { + tail = new Aff2(CONS, head2, tail); } - head = new Aff2(ALT, EMPTY, step2._2, EMPTY); + head2 = new Aff2(ALT, EMPTY, step2._2, EMPTY); step2 = step2._1; break; default: fid = fiberId++; status2 = RETURN; tmp = step2; - step2 = new Aff2(FORKED, fid, new Aff2(CONS, head, tail), EMPTY); + step2 = new Aff2(FORKED, fid, new Aff2(CONS, head2, tail), EMPTY); tmp = Fiber(util, supervisor, tmp); tmp.onComplete({ rethrow: false, @@ -1436,21 +1487,21 @@ var Aff = function() { } break; case RETURN: - if (head === null) { + if (head2 === null) { break loop; } - if (head._1 === EMPTY) { - head._1 = step2; + if (head2._1 === EMPTY) { + head2._1 = step2; status2 = CONTINUE; - step2 = head._2; - head._2 = EMPTY; + step2 = head2._2; + head2._2 = EMPTY; } else { - head._2 = step2; - step2 = head; + head2._2 = step2; + step2 = head2; if (tail === null) { - head = null; + head2 = null; } else { - head = tail._1; + head2 = tail._1; tail = tail._2; } } @@ -1595,10 +1646,10 @@ var mapExceptT = function(f) { }; }; var functorExceptT = function(dictFunctor) { - var map12 = map(dictFunctor); + var map13 = map(dictFunctor); return { map: function(f) { - return mapExceptT(map12(map2(f))); + return mapExceptT(map13(map2(f))); } }; }; @@ -1677,7 +1728,7 @@ var parallel = function(dict) { }; // output/Control.Parallel/index.js -var identity5 = /* @__PURE__ */ identity(categoryFn); +var identity6 = /* @__PURE__ */ identity(categoryFn); var parTraverse_ = function(dictParallel) { var sequential2 = sequential(dictParallel); var parallel3 = parallel(dictParallel); @@ -1701,7 +1752,7 @@ var parSequence_ = function(dictParallel) { return function(dictApplicative) { var parTraverse_2 = parTraverse_1(dictApplicative); return function(dictFoldable) { - return parTraverse_2(dictFoldable)(identity5); + return parTraverse_2(dictFoldable)(identity6); }; }; }; @@ -2681,11 +2732,8 @@ var fetch2 = function() { // output/Model/foreign.js var canvas = document.getElementById("lcolonq-canvas"); -var socket = new WebSocket("wss://colonq.computer/bullfrog/api/channel/listen/model"); +var socket = null; var currentFrame = null; -socket.addEventListener("open", (ev) => { - console.log("connected"); -}); async function decompress(blob3) { let ds = new DecompressionStream("gzip"); let stream = blob3.stream(); @@ -2743,7 +2791,7 @@ function readPacket(dv) { } } function renderCellCanvas(ctx2, x, y, c) { - if (c) { + if (c && c.type === "fg") { let msg = c.g1 ? String.fromCodePoint(c.g0, c.g1) : String.fromCodePoint(c.g0); if (msg.trim().length) { ctx2.fillStyle = "black"; @@ -2772,6 +2820,10 @@ function renderCanvas() { } } var _startModel = () => { + socket = new WebSocket("wss://colonq.computer/bullfrog/api/channel/listen/model"); + socket.addEventListener("open", (ev) => { + console.log("connected"); + }); socket.addEventListener("message", async (ev) => { let arr = await decompress(ev.data); let view = new DataView(arr); @@ -2785,6 +2837,38 @@ var startModel = function(dictMonadEffect) { return liftEffect(dictMonadEffect)(_startModel); }; +// output/UI/foreign.js +var _setInterval = (delay) => (f) => () => setInterval(f, delay); + +// output/UI/index.js +var setInterval2 = function(dictMonadEffect) { + var liftEffect6 = liftEffect(dictMonadEffect); + return function(d) { + return function(f) { + return liftEffect6(_setInterval(d)(f)); + }; + }; +}; + +// output/Web.DOM.DOMTokenList/foreign.js +function add2(list) { + return function(token) { + return function() { + return list.add(token); + }; + }; +} + +// output/Data.Nullable/foreign.js +function nullable(a, r, f) { + return a == null ? r : f(a); +} + +// output/Data.Nullable/index.js +var toMaybe = function(n) { + return nullable(n, Nothing.value, Just.create); +}; + // output/Web.DOM.Document/foreign.js var getEffProp = function(name15) { return function(doc) { @@ -2800,18 +2884,52 @@ var compatMode = getEffProp("compatMode"); var characterSet = getEffProp("characterSet"); var contentType = getEffProp("contentType"); var _documentElement = getEffProp("documentElement"); +function createElement(localName2) { + return function(doc) { + return function() { + return doc.createElement(localName2); + }; + }; +} +function createTextNode(data) { + return function(doc) { + return function() { + return doc.createTextNode(data); + }; + }; +} -// output/Data.Nullable/foreign.js -function nullable(a, r, f) { - return a == null ? r : f(a); +// output/Web.Internal.FFI/foreign.js +function _unsafeReadProtoTagged(nothing, just, name15, value12) { + if (typeof window !== "undefined") { + var ty = window[name15]; + if (ty != null && value12 instanceof ty) { + return just(value12); + } + } + var obj = value12; + while (obj != null) { + var proto = Object.getPrototypeOf(obj); + var constructorName = proto.constructor.name; + if (constructorName === name15) { + return just(value12); + } else if (constructorName === "Object") { + return nothing; + } + obj = proto; + } + return nothing; } -// output/Data.Nullable/index.js -var toMaybe = function(n) { - return nullable(n, Nothing.value, Just.create); +// output/Web.Internal.FFI/index.js +var unsafeReadProtoTagged = function(name15) { + return function(value12) { + return _unsafeReadProtoTagged(Nothing.value, Just.create, name15, value12); + }; }; // output/Web.DOM.Document/index.js +var toParentNode = unsafeCoerce2; var toNonElementParentNode = unsafeCoerce2; // output/Web.DOM.Element/foreign.js @@ -2824,6 +2942,11 @@ var _namespaceURI = getProp("namespaceURI"); var _prefix = getProp("prefix"); var localName = getProp("localName"); var tagName = getProp("tagName"); +function classList(element) { + return function() { + return element.classList; + }; +} // output/Web.DOM.ParentNode/foreign.js var getEffProp2 = function(name15) { @@ -2837,10 +2960,18 @@ var children = getEffProp2("children"); var _firstElementChild = getEffProp2("firstElementChild"); var _lastElementChild = getEffProp2("lastElementChild"); var childElementCount = getEffProp2("childElementCount"); +function querySelectorAll(selector) { + return function(node) { + return function() { + return node.querySelectorAll(selector); + }; + }; +} // output/Web.DOM.Element/index.js var toNode = unsafeCoerce2; var toEventTarget = unsafeCoerce2; +var fromNode = /* @__PURE__ */ unsafeReadProtoTagged("Element"); // output/Web.DOM.Node/foreign.js var getEffProp3 = function(name15) { @@ -2868,6 +2999,20 @@ function setTextContent(value12) { }; }; } +function appendChild(node) { + return function(parent2) { + return function() { + parent2.appendChild(node); + }; + }; +} + +// output/Web.DOM.NodeList/foreign.js +function toArray2(list) { + return function() { + return [].slice.call(list); + }; +} // output/Web.DOM.NonElementParentNode/foreign.js function _getElementById(id2) { @@ -2888,6 +3033,9 @@ var getElementById = function(eid) { }; }; +// output/Web.DOM.Text/index.js +var toNode2 = unsafeCoerce2; + // output/Web.Event.EventTarget/foreign.js function eventListener(fn) { return function() { @@ -2925,30 +3073,89 @@ function document2(window2) { // output/Main/index.js var map6 = /* @__PURE__ */ map(functorEffect); +var fold2 = /* @__PURE__ */ fold(foldableArray)(monoidArray); +var map12 = /* @__PURE__ */ map(functorArray); +var startModel2 = /* @__PURE__ */ startModel(monadEffectAff); var bind4 = /* @__PURE__ */ bind(bindAff); var fetch3 = /* @__PURE__ */ fetch2()()(/* @__PURE__ */ toCoreRequestOptionsRowRo()()(toCoreRequestOptionsHelpe)); -var discard2 = /* @__PURE__ */ discard(discardUnit)(bindAff); -var startModel2 = /* @__PURE__ */ startModel(monadEffectAff); +var discard2 = /* @__PURE__ */ discard(discardUnit); +var discard1 = /* @__PURE__ */ discard2(bindAff); +var liftEffect5 = /* @__PURE__ */ liftEffect(monadEffectAff); var for_2 = /* @__PURE__ */ for_(applicativeAff)(foldableArray); var show2 = /* @__PURE__ */ show(showInt); var playVoice2 = /* @__PURE__ */ playVoice(monadEffectEffect); +var setInterval3 = /* @__PURE__ */ setInterval2(monadEffectAff); var setText5 = function(dictMonadEffect) { - var liftEffect5 = liftEffect(dictMonadEffect); + var liftEffect1 = liftEffect(dictMonadEffect); return function(e) { return function(s) { - return liftEffect5(setTextContent(s)(toNode(e))); + return liftEffect1(setTextContent(s)(toNode(e))); }; }; }; var setText1 = /* @__PURE__ */ setText5(monadEffectAff); +var maybeToArray = function(v) { + if (v instanceof Just) { + return [v.value0]; + } + ; + if (v instanceof Nothing) { + return []; + } + ; + throw new Error("Failed pattern match at Main (line 35, column 1 - line 35, column 45): " + [v.constructor.name]); +}; +var queryAll = function(dictMonadEffect) { + var Monad0 = dictMonadEffect.Monad0(); + var bind22 = bind(Monad0.Bind1()); + var liftEffect1 = liftEffect(dictMonadEffect); + var pure3 = pure(Monad0.Applicative0()); + return function(q) { + return bind22(liftEffect1(windowImpl))(function(w) { + return bind22(liftEffect1(map6(toDocument)(document2(w))))(function(d) { + return bind22(liftEffect1(querySelectorAll(q)(toParentNode(d))))(function(nl) { + return bind22(liftEffect1(toArray2(nl)))(function(ns) { + return pure3(fold2(map12(function($95) { + return maybeToArray(fromNode($95)); + })(ns))); + }); + }); + }); + }); + }; +}; +var query = function(dictMonadEffect) { + var Monad0 = dictMonadEffect.Monad0(); + var bind22 = bind(Monad0.Bind1()); + var queryAll1 = queryAll(dictMonadEffect); + var liftEffect1 = liftEffect(dictMonadEffect); + var pure3 = pure(Monad0.Applicative0()); + return function(q) { + return bind22(queryAll1(q))(function($96) { + return function(v) { + if (v instanceof Nothing) { + return liftEffect1($$throw("could not find element matching query: " + q)); + } + ; + if (v instanceof Just) { + return pure3(v.value0); + } + ; + throw new Error("Failed pattern match at Main (line 57, column 27 - line 59, column 21): " + [v.constructor.name]); + }(head($96)); + }); + }; +}; +var query1 = /* @__PURE__ */ query(monadEffectEffect); +var mainObs = /* @__PURE__ */ launchAff_(startModel2); var listen2 = function(dictMonadEffect) { - var bind1 = bind(dictMonadEffect.Monad0().Bind1()); - var liftEffect5 = liftEffect(dictMonadEffect); + var bind22 = bind(dictMonadEffect.Monad0().Bind1()); + var liftEffect1 = liftEffect(dictMonadEffect); return function(e) { return function(ev) { return function(f) { - return bind1(liftEffect5(eventListener(f)))(function(l) { - return liftEffect5(addEventListener(ev)(l)(false)(toEventTarget(e))); + return bind22(liftEffect1(eventListener(f)))(function(l) { + return liftEffect1(addEventListener(ev)(l)(false)(toEventTarget(e))); }); }; }; @@ -2957,22 +3164,22 @@ var listen2 = function(dictMonadEffect) { var listen1 = /* @__PURE__ */ listen2(monadEffectAff); var byId = function(dictMonadEffect) { var Monad0 = dictMonadEffect.Monad0(); - var bind1 = bind(Monad0.Bind1()); - var liftEffect5 = liftEffect(dictMonadEffect); + var bind22 = bind(Monad0.Bind1()); + var liftEffect1 = liftEffect(dictMonadEffect); var pure3 = pure(Monad0.Applicative0()); return function(i) { - return bind1(liftEffect5(windowImpl))(function(w) { - return bind1(liftEffect5(map6(toDocument)(document2(w))))(function(d) { - return bind1(liftEffect5(getElementById(i)(toNonElementParentNode(d))))(function(v) { + return bind22(liftEffect1(windowImpl))(function(w) { + return bind22(liftEffect1(map6(toDocument)(document2(w))))(function(d) { + return bind22(liftEffect1(getElementById(i)(toNonElementParentNode(d))))(function(v) { if (v instanceof Nothing) { - return liftEffect5($$throw("could not find element with id: " + i)); + return liftEffect1($$throw("could not find element with id: " + i)); } ; if (v instanceof Just) { return pure3(v.value0); } ; - throw new Error("Failed pattern match at Main (line 32, column 80 - line 34, column 21): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 43, column 80 - line 45, column 21): " + [v.constructor.name]); }); }); }); @@ -2984,19 +3191,19 @@ var updateSubtitle = /* @__PURE__ */ bind4(/* @__PURE__ */ byId1("lcolonq-subtit return bind4(v.text)(setText1(subtitle)); }); }); -var main = /* @__PURE__ */ launchAff_(/* @__PURE__ */ discard2(/* @__PURE__ */ liftEffect(monadEffectAff)(/* @__PURE__ */ log("hi")))(function() { - return discard2(startModel2)(function() { +var mainHomepage = /* @__PURE__ */ launchAff_(/* @__PURE__ */ discard1(/* @__PURE__ */ liftEffect5(/* @__PURE__ */ log("hi")))(function() { + return discard1(startModel2)(function() { return bind4(byId1("lcolonq-marquee"))(function(marq) { return bind4(fetch3(apiServer + "/motd")({}))(function(v) { - return discard2(bind4(v.text)(setText1(marq)))(function() { - return discard2(updateSubtitle)(function() { + return discard1(bind4(v.text)(setText1(marq)))(function() { + return discard1(updateSubtitle)(function() { return bind4(byId1("lcolonq-subtitle"))(function(subtitle) { - return discard2(listen1(subtitle)("click")(function(_ev) { + return discard1(listen1(subtitle)("click")(function(_ev) { return launchAff_(updateSubtitle); }))(function() { return for_2(range2(0)(6))(function(i) { return bind4(byId1("lcolonq-letter-" + show2(i)))(function(letter) { - return discard2(listen1(letter)("click")(function(_ev) { + return discard1(listen1(letter)("click")(function(_ev) { return playVoice2(true)(i); }))(function() { return listen1(letter)("mouseover")(function(_ev) { @@ -3013,6 +3220,89 @@ var main = /* @__PURE__ */ launchAff_(/* @__PURE__ */ discard2(/* @__PURE__ */ l }); }); })); +var appendText = function(dictMonadEffect) { + var bind22 = bind(dictMonadEffect.Monad0().Bind1()); + var liftEffect1 = liftEffect(dictMonadEffect); + return function(parent2) { + return function(s) { + return bind22(liftEffect1(windowImpl))(function(w) { + return bind22(liftEffect1(map6(toDocument)(document2(w))))(function(d) { + return bind22(liftEffect1(createTextNode(s)(d)))(function(n) { + return liftEffect1(appendChild(toNode2(n))(toNode(parent2))); + }); + }); + }); + }; + }; +}; +var appendText1 = /* @__PURE__ */ appendText(monadEffectEffect); +var appendElement = function(dictMonadEffect) { + var liftEffect1 = liftEffect(dictMonadEffect); + return function(parent2) { + return function(child) { + return liftEffect1(appendChild(toNode(child))(toNode(parent2))); + }; + }; +}; +var appendElement1 = /* @__PURE__ */ appendElement(monadEffectEffect); +var create3 = function(dictMonadEffect) { + var Monad0 = dictMonadEffect.Monad0(); + var Bind1 = Monad0.Bind1(); + var bind22 = bind(Bind1); + var liftEffect1 = liftEffect(dictMonadEffect); + var discard3 = discard2(Bind1); + var Applicative0 = Monad0.Applicative0(); + var for_1 = for_(Applicative0)(foldableArray); + var appendElement2 = appendElement(dictMonadEffect); + var pure3 = pure(Applicative0); + return function(tag) { + return function(classes) { + return function(children2) { + return bind22(liftEffect1(windowImpl))(function(w) { + return bind22(liftEffect1(map6(toDocument)(document2(w))))(function(d) { + return bind22(liftEffect1(createElement(tag)(d)))(function(el) { + return bind22(liftEffect1(classList(el)))(function(cl) { + return discard3(for_1(classes)(function(c) { + return liftEffect1(add2(cl)(c)); + }))(function() { + return discard3(for_1(children2)(function(c) { + return appendElement2(el)(c); + }))(function() { + return pure3(el); + }); + }); + }); + }); + }); + }); + }; + }; + }; +}; +var create1 = /* @__PURE__ */ create3(monadEffectEffect); +var mainExtension = /* @__PURE__ */ launchAff_(/* @__PURE__ */ discard1(/* @__PURE__ */ liftEffect5(/* @__PURE__ */ log("hello from extension")))(function() { + return setInterval3(1e3)(function __do() { + var e = query1(".chat-scrollable-area__message-container")(); + var $$new5 = create1("div")([".chat-line__message"])([])(); + appendText1($$new5)("test")(); + return appendElement1(e)($$new5)(); + }); +})); +var main = /* @__PURE__ */ function() { + if (mode === 0) { + return mainHomepage; + } + ; + if (mode === 1) { + return mainExtension; + } + ; + if (mode === 2) { + return mainObs; + } + ; + return $$throw("unknown mode"); +}(); // <stdin> main(); diff --git a/fig-frontend-client/src/Config.js b/fig-frontend-client/src/Config.js index 11a9792..0990a53 100644 --- a/fig-frontend-client/src/Config.js +++ b/fig-frontend-client/src/Config.js @@ -1 +1,2 @@ +export const mode = globalThis.mode; export const apiServer = globalThis.apiServer; diff --git a/fig-frontend-client/src/Config.purs b/fig-frontend-client/src/Config.purs index b464803..3711d69 100644 --- a/fig-frontend-client/src/Config.purs +++ b/fig-frontend-client/src/Config.purs @@ -1,3 +1,4 @@ module Config where +foreign import mode :: Int foreign import apiServer :: String diff --git a/fig-frontend-client/src/Main.purs b/fig-frontend-client/src/Main.purs index cb06b86..8b132a7 100644 --- a/fig-frontend-client/src/Main.purs +++ b/fig-frontend-client/src/Main.purs @@ -4,7 +4,9 @@ import Prelude import Audio as Audio import Config as Config +import Data.Array (head) import Data.Array as Array +import Data.Foldable (fold) import Data.Maybe (Maybe(..)) import Data.Traversable (for, for_) import Effect (Effect) @@ -14,17 +16,26 @@ import Effect.Console (log) import Effect.Exception (throw) import Fetch (fetch) import Model (startModel) +import UI as UI import Web.DOM as DOM +import Web.DOM.DOMTokenList as DOM.DTL import Web.DOM.Document as DOM.Doc import Web.DOM.Element as DOM.El import Web.DOM.Node as DOM.Node +import Web.DOM.NodeList as DOM.NL import Web.DOM.NonElementParentNode as DOM.NEP +import Web.DOM.Text as DOM.Text +import Web.DOM.ParentNode as DOM.P import Web.Event.Event as Ev import Web.Event.EventTarget as Ev.Tar import Web.HTML as HTML import Web.HTML.HTMLDocument as HTML.Doc import Web.HTML.Window as HTML.Win +maybeToArray :: forall a. Maybe a -> Array a +maybeToArray (Just x) = [x] +maybeToArray Nothing = [] + byId :: forall m. MonadEffect m => String -> m DOM.Element byId i = do w <- liftEffect HTML.window @@ -33,11 +44,47 @@ byId i = do Nothing -> liftEffect $ throw $ "could not find element with id: " <> i Just e -> pure e +queryAll :: forall m. MonadEffect m => String -> m (Array DOM.Element) +queryAll q = do + w <- liftEffect HTML.window + d <- liftEffect $ HTML.Doc.toDocument <$> HTML.Win.document w + nl <- liftEffect (DOM.P.querySelectorAll (DOM.P.QuerySelector q) (DOM.Doc.toParentNode d)) + ns <- liftEffect $ DOM.NL.toArray nl + pure $ fold $ (maybeToArray <<< DOM.El.fromNode) <$> ns + +query :: forall m . MonadEffect m => String -> m DOM.Element +query q = do + queryAll q >>= head >>> case _ of + Nothing -> liftEffect $ throw $ "could not find element matching query: " <> q + Just x -> pure x + listen :: forall m. MonadEffect m => DOM.Element -> String -> (Ev.Event -> Effect Unit) -> m Unit listen e ev f = do l <- liftEffect $ Ev.Tar.eventListener f liftEffect $ Ev.Tar.addEventListener (Ev.EventType ev) l false $ DOM.El.toEventTarget e +create :: forall m. MonadEffect m => String -> Array String -> Array DOM.Element -> m DOM.Element +create tag classes children = do + w <- liftEffect HTML.window + d <- liftEffect $ HTML.Doc.toDocument <$> HTML.Win.document w + el <- liftEffect $ DOM.Doc.createElement tag d + cl <- liftEffect $ DOM.El.classList el + for_ classes \c -> + liftEffect $ DOM.DTL.add cl c + for_ children \c -> + appendElement el c + pure el + +appendElement :: forall m. MonadEffect m => DOM.Element -> DOM.Element -> m Unit +appendElement parent child = liftEffect $ DOM.Node.appendChild (DOM.El.toNode child) (DOM.El.toNode parent) + +appendText :: forall m. MonadEffect m => DOM.Element -> String -> m Unit +appendText parent s = do + w <- liftEffect HTML.window + d <- liftEffect $ HTML.Doc.toDocument <$> HTML.Win.document w + n <- liftEffect $ DOM.Doc.createTextNode s d + liftEffect $ DOM.Node.appendChild (DOM.Text.toNode n) (DOM.El.toNode parent) + setText :: forall m. MonadEffect m => DOM.Element -> String -> m Unit setText e s = liftEffect $ DOM.Node.setTextContent s $ DOM.El.toNode e @@ -47,8 +94,8 @@ updateSubtitle = do { text: catchphrase } <- fetch (Config.apiServer <> "/catchphrase") {} catchphrase >>= setText subtitle -main :: Effect Unit -main = launchAff_ do +mainHomepage :: Effect Unit +mainHomepage = launchAff_ do liftEffect $ log "hi" startModel marq <- byId "lcolonq-marquee" @@ -66,3 +113,23 @@ main = launchAff_ do Audio.playVoice true i listen letter "mouseover" \_ev -> do Audio.playVoice false i + +mainExtension :: Effect Unit +mainExtension = launchAff_ do + liftEffect $ log "hello from extension" + UI.setInterval 1000.0 do + e <- query ".chat-scrollable-area__message-container" + new <- create "div" [".chat-line__message"] [] + appendText new "test" + appendElement e new + +mainObs :: Effect Unit +mainObs = launchAff_ do + startModel + +main :: Effect Unit +main = case Config.mode of + 0 -> mainHomepage + 1 -> mainExtension + 2 -> mainObs + _ -> throw "unknown mode" diff --git a/fig-frontend-client/src/Model.js b/fig-frontend-client/src/Model.js index 299c645..797bae4 100644 --- a/fig-frontend-client/src/Model.js +++ b/fig-frontend-client/src/Model.js @@ -1,11 +1,7 @@ let canvas = document.getElementById("lcolonq-canvas"); -let socket = new WebSocket("wss://colonq.computer/bullfrog/api/channel/listen/model"); +let socket = null; let currentFrame = null; -socket.addEventListener("open", (ev) => { - console.log("connected"); -}); - async function decompress(blob) { let ds = new DecompressionStream("gzip"); let stream = blob.stream(); @@ -68,7 +64,7 @@ function readPacket(dv) { } function renderCellCanvas(ctx, x, y, c) { - if (c) { + if (c && c.type === "fg") { let msg = c.g1 ? String.fromCodePoint(c.g0, c.g1) : String.fromCodePoint(c.g0); if (msg.trim().length) { ctx.fillStyle = "black"; @@ -99,6 +95,10 @@ function renderCanvas() { } export const _startModel = () => { + socket = new WebSocket("wss://colonq.computer/bullfrog/api/channel/listen/model"); + socket.addEventListener("open", (ev) => { + console.log("connected"); + }); socket.addEventListener("message", async (ev) => { let arr = await decompress(ev.data); let view = new DataView(arr); diff --git a/fig-frontend-client/src/UI.js b/fig-frontend-client/src/UI.js new file mode 100644 index 0000000..0b00e5d --- /dev/null +++ b/fig-frontend-client/src/UI.js @@ -0,0 +1,2 @@ +export const _cheatLog = (a) => () => console.log(a); +export const _setInterval = (delay) => (f) => () => setInterval(f, delay); diff --git a/fig-frontend-client/src/UI.purs b/fig-frontend-client/src/UI.purs new file mode 100644 index 0000000..49d797a --- /dev/null +++ b/fig-frontend-client/src/UI.purs @@ -0,0 +1,13 @@ +module UI where + +import Prelude +import Effect (Effect) +import Effect.Class (class MonadEffect, liftEffect) + +foreign import _cheatLog :: forall a. a -> Effect Unit +cheatLog :: forall m a. MonadEffect m => a -> m Unit +cheatLog x = liftEffect $ _cheatLog x + +foreign import _setInterval :: Number -> Effect Unit -> Effect Unit +setInterval :: forall m. MonadEffect m => Number -> Effect Unit -> m Unit +setInterval d f = liftEffect $ _setInterval d f diff --git a/fig-frontend-client/index-template.html b/fig-frontend-client/templates/index.html index f793ddd..8827694 100644 --- a/fig-frontend-client/index-template.html +++ b/fig-frontend-client/templates/index.html @@ -11,7 +11,7 @@ CONFIG_SUBST </script> <script type="module" src="./main.js"></script> </head> - <body> + <body class="lcolonq-index"> <!-- RINGBEARER --> <canvas id="lcolonq-canvas"></canvas> <div id="lcolonq-title"> diff --git a/fig-frontend-client/templates/obs.html b/fig-frontend-client/templates/obs.html new file mode 100644 index 0000000..999035a --- /dev/null +++ b/fig-frontend-client/templates/obs.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> + <title>LCOLONQ Browser Source</title> + <link rel="icon" href="./assets/mrgreen.png"> + <link rel="stylesheet" type="text/css" href="./main.css"> + <script type="module"> +CONFIG_SUBST + globalThis.mode = 2; + </script> + <script type="module" src="./main.js"></script> + </head> + <body class="lcolonq-obs"> + <canvas id="lcolonq-canvas"></canvas> + </body> +</html> diff --git a/fig-monitor-twitch/main/Main.hs b/fig-monitor-twitch/main/Main.hs index 2232a03..05f3cdb 100644 --- a/fig-monitor-twitch/main/Main.hs +++ b/fig-monitor-twitch/main/Main.hs @@ -11,12 +11,14 @@ data Command = Monitor | Chatbot | RedirectServer + | Validate parseCommand :: Parser Command parseCommand = subparser $ mconcat [ command "monitor" $ info (pure Monitor) (progDesc "Launch the Twitch monitor") , command "chatbot" $ info (pure Chatbot) (progDesc "Launch the Twitch chatbot") , command "user-token-server" $ info (pure RedirectServer) (progDesc "Launch a web server to handle authentication redirects") + , command "validate-endpoint" $ info (pure Validate) (progDesc "Test Twitch authentication") ] data Opts = Opts { busHost :: Text @@ -43,3 +45,4 @@ main = do Monitor -> twitchEventClient cfg (opts.busHost, opts.busPort) Chatbot -> twitchChatClient cfg (opts.busHost, opts.busPort) RedirectServer -> userTokenRedirectServer cfg + Validate -> twitchEndpointTest cfg diff --git a/fig-monitor-twitch/src/Fig/Monitor/Twitch.hs b/fig-monitor-twitch/src/Fig/Monitor/Twitch.hs index 1889545..17f2b8a 100644 --- a/fig-monitor-twitch/src/Fig/Monitor/Twitch.hs +++ b/fig-monitor-twitch/src/Fig/Monitor/Twitch.hs @@ -5,6 +5,7 @@ module Fig.Monitor.Twitch ( twitchEventClient , twitchChatClient + , twitchEndpointTest , userTokenRedirectServer ) where @@ -233,6 +234,11 @@ shoutout souser user = do unless (HTTP.statusIsSuccessful $ HTTP.responseStatus response) $ do log $ "Failed to shoutout: error " <> tshow (HTTP.statusCode $ HTTP.responseStatus response) +twitchEndpointTest :: Config -> IO () +twitchEndpointTest cfg = runAuthed cfg do + user <- loginToUserId "lcolonq" + log user + twitchEventClient :: Config -> (Text, Text) -> IO () twitchEventClient cfg busAddr = do WS.runSecureClient "eventsub.wss.twitch.tv" 443 "/ws" \conn -> do @@ -248,6 +254,7 @@ twitchEventClient cfg busAddr = do log $ "Connected to Twitch API, session ID is: " <> sessionId runAuthed cfg do user <- loginToUserId cfg.userLogin + log "got user id" subscribe sessionId "channel.channel_points_custom_reward_redemption.add" user subscribe sessionId "channel.prediction.begin" user subscribe sessionId "channel.prediction.end" user @@ -255,6 +262,7 @@ twitchEventClient cfg busAddr = do subscribe sessionId "channel.poll.end" user subscribe sessionId "channel.subscribe" user subscribe sessionId "channel.subscription.gift" user + log "finished subscribing" subscribeFollows sessionId user subscribeRaids sessionId user busClient busAddr @@ -275,7 +283,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - nm <- event .: "user_name" + nm <- event .: "user_login" reward <- event .: "reward" title <- reward .: "title" minput <- event .:? "user_input" @@ -312,7 +320,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - event .: "from_broadcaster_user_name" + event .: "from_broadcaster_user_login" case Aeson.parseMaybe parseEvent res of Just nm -> do log $ "Incoming raid from: " <> nm @@ -322,7 +330,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - event .: "user_name" + event .: "user_login" case Aeson.parseMaybe parseEvent res of Just nm -> do log $ "New follower: " <> nm @@ -332,7 +340,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - event .: "user_name" + event .: "user_login" case Aeson.parseMaybe parseEvent res of Just nm -> do log $ "New subscriber: " <> nm @@ -342,7 +350,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - nm <- event .: "user_name" + nm <- event .: "user_login" bits <- event .: "bits" pure (nm, bits) case Aeson.parseMaybe parseEvent res of @@ -354,7 +362,7 @@ twitchEventClient cfg busAddr = do let parseEvent o = do payload <- o .: "payload" event <- payload .: "event" - nm <- event .: "user_name" + nm <- event .: "user_login" num <- event .: "total" pure (nm, num) case Aeson.parseMaybe parseEvent res of diff --git a/fig-monitor-twitch/src/Fig/Monitor/Twitch/Utils.hs b/fig-monitor-twitch/src/Fig/Monitor/Twitch/Utils.hs index f1d757c..59ba04c 100644 --- a/fig-monitor-twitch/src/Fig/Monitor/Twitch/Utils.hs +++ b/fig-monitor-twitch/src/Fig/Monitor/Twitch/Utils.hs @@ -78,7 +78,8 @@ authedRequestJSON :: (Aeson.ToJSON a, Aeson.FromJSON b) => Text -> Text -> a -> authedRequestJSON method url val = do resp <- authedRequest method url $ Aeson.encode val case Aeson.eitherDecode resp of - Left err -> throwM . FigMonitorTwitchException $ tshow err + Left err -> do + throwM . FigMonitorTwitchException $ tshow err Right res -> pure res runAuthed :: Config -> Authed a -> IO a |
