diff options
| author | LLLL Colonq <llll@colonq> | 2025-09-16 01:33:43 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2025-09-16 01:33:43 -0400 |
| commit | fe903c535211bdbeeb703e06db0da3f7c8c19b4b (patch) | |
| tree | 66c383a14eb92f61df32f2407719a761e4c2ed16 /src | |
| parent | 9dec5e4d54ecbfb84ef8eba727b44bb6435f6e40 (diff) | |
Update
Diffstat (limited to 'src')
| -rw-r--r-- | src/gizmo/wasp-flycheck.el | 17 | ||||
| -rw-r--r-- | src/gizmo/wasp-flymake.el | 63 | ||||
| -rw-r--r-- | src/gizmo/wasp-friend.el | 4 | ||||
| -rw-r--r-- | src/gizmo/wasp-genealogy.el | 139 | ||||
| -rw-r--r-- | src/gizmo/wasp-irish.el | 370 | ||||
| -rw-r--r-- | src/wasp-ai.el | 38 | ||||
| -rw-r--r-- | src/wasp-audio.el | 7 | ||||
| -rw-r--r-- | src/wasp-chat.el | 7 | ||||
| -rw-r--r-- | src/wasp-event-handlers-binary.el | 3 | ||||
| -rw-r--r-- | src/wasp-model.el | 35 | ||||
| -rw-r--r-- | src/wasp-overlay.el | 9 | ||||
| -rw-r--r-- | src/wasp-setup.el | 2 | ||||
| -rw-r--r-- | src/wasp-soundboard.el | 83 | ||||
| -rw-r--r-- | src/wasp-twitch-chat-commands.el | 4 | ||||
| -rw-r--r-- | src/wasp-twitch-redeems.el | 38 | ||||
| -rw-r--r-- | src/wasp-twitch.el | 23 | ||||
| -rw-r--r-- | src/wasp-user-stats.el | 4 | ||||
| -rw-r--r-- | src/wasp-user-whitelist.el | 27 | ||||
| -rw-r--r-- | src/wasp-utils.el | 47 |
19 files changed, 857 insertions, 63 deletions
diff --git a/src/gizmo/wasp-flycheck.el b/src/gizmo/wasp-flycheck.el deleted file mode 100644 index 505ad8a3..00000000 --- a/src/gizmo/wasp-flycheck.el +++ /dev/null @@ -1,17 +0,0 @@ -;;; wasp-flycheck --- Flycheck -*- lexical-binding: t; -*- -;;; Commentary: -;;; Code: - -;; (require 'flycheck) -;; -;; (flycheck-define-generic-checker 'wasp-twitch -;; "Checker to display errors from Twitch redeems." -;; :start -;; (lambda (c x) -;; (print c) -;; (print x)) -;; :modes '(fundamental-mode) -;; ) - -(provide 'wasp-flycheck) -;;; wasp-flycheck.el ends here diff --git a/src/gizmo/wasp-flymake.el b/src/gizmo/wasp-flymake.el new file mode 100644 index 00000000..42c085b4 --- /dev/null +++ b/src/gizmo/wasp-flymake.el @@ -0,0 +1,63 @@ +;;; wasp-flymake --- Flymake backend -*- lexical-binding: t; -*- +;;; Commentary: +;;; Code: +(require 'dash) +(require 'ht) +(require 's) +(require 'flymake) + +(defvar w/flymake-errors (ht-create)) + +(defun w/flymake-thing-bounds (pos) + "Return the bounds of the thing to highlight at POS." + (save-excursion + (goto-char pos) + (or + (bounds-of-thing-at-point 'symbol) + (bounds-of-thing-at-point 'sexp) + (bounds-of-thing-at-point 'line)))) + +(defun w/flymake-thing (pos) + "Return the thing highlighted at POS." + (-let [(begin . end) (w/flymake-thing-bounds pos)] + (buffer-substring-no-properties begin end))) + +(defun w/flymake-error (user msg) + "Create a new Flymake error at the cursor from USER saying MSG." + (when (and flymake-mode (buffer-file-name)) + (push (list (point) (w/flymake-thing (point)) user msg) + (ht-get w/flymake-errors (buffer-file-name))) + (flymake-start))) + +(defun w/flymake-prune () + "Remove invalidated Flymake errors." + (let* + ( (errs (ht-get w/flymake-errors (buffer-file-name))) + (pruned + (--filter + (when-let* ((sym (w/flymake-thing (car it)))) + (s-equals? sym (cadr it))) + errs))) + (ht-set! w/flymake-errors (buffer-file-name) pruned))) + +(defun w/flymake-backend (report-fn &rest _) + "Flymake backend for stream errors. Calls REPORT-FN." + (w/flymake-prune) + (-let [errs (ht-get w/flymake-errors (buffer-file-name))] + (funcall report-fn + (--map + (-let [(begin . end) (w/flymake-thing-bounds (car it))] + (flymake-make-diagnostic + (current-buffer) + begin end + :note + (format "%s: %s" (caddr it) (cadddr it)))) + errs)))) + +(defun w/flymake-setup () + "Setup stream Flymake errors." + (add-hook 'flymake-diagnostic-functions #'w/flymake-backend nil t)) +(add-hook 'prog-mode-hook #'w/flymake-setup) + +(provide 'wasp-flymake) +;;; wasp-flymake.el ends here diff --git a/src/gizmo/wasp-friend.el b/src/gizmo/wasp-friend.el index 28403025..aff6e358 100644 --- a/src/gizmo/wasp-friend.el +++ b/src/gizmo/wasp-friend.el @@ -54,8 +54,8 @@ (w/daily-log (format "[FRIEND]: %s" msg)) (w/gizmo-ensure-shown (w/friend-get-buffer)) (w/friend-pronounce-phonemes (w/friend-replace-graphemes msg)) - (w/friend-set-speech msg 10) - (w/friend-set-state 'chatting 10)) + (w/friend-set-speech msg 30) + (w/friend-set-state 'chatting 30)) ;;;; Core (defun w/friend-personality (msg k &optional extra) diff --git a/src/gizmo/wasp-genealogy.el b/src/gizmo/wasp-genealogy.el new file mode 100644 index 00000000..56bddd06 --- /dev/null +++ b/src/gizmo/wasp-genealogy.el @@ -0,0 +1,139 @@ +;;; wasp-genealogy --- Colonq Family Genealogy -*- lexical-binding: t; -*- +;;; Commentary: +;;; Code: + +(require 'wasp-ai) + +(require 'dash) +(require 's) +(require 'f) +(require 'ht) +(require 'cl-lib) + +(load (w/asset "irish/genealogy-data.el")) + +(cl-defstruct + (w/ancestor + (:constructor w/make-ancestor)) + title + firstname + middlename + nickname + namesake + job + dateofbirth + dateofdeath + causeofdeath) + +(defun w/ancestor-serialize (a) + "Serialize an ancestor A." + (list + (w/ancestor-title a) + (w/ancestor-firstname a) + (w/ancestor-middlename a) + (w/ancestor-nickname a) + (w/ancestor-namesake a) + (w/ancestor-job a) + (w/ancestor-dateofbirth a) + (w/ancestor-dateofdeath a) + (w/ancestor-causeofdeath a))) + +(defun w/generate-name (era) + "Determine an appropriate name from ERA." + (let ((names (alist-get era w/era-names))) + (nth (random (length names)) names))) + +(defun w/generate-job (era) + "Determine an appropriate job from ERA." + (let ((jobs (alist-get era w/era-jobs))) + (nth (random (length jobs)) jobs))) + +(defun w/generate-nickname (era) + "Determine an appropriate job from ERA." + (let ((nicknames (alist-get era w/era-nicknames))) + (nth (random (length nicknames)) nicknames))) + +(defun w/generate-birth-year () + "Determine which year the astrally-focused ancestor was born." + (+ 1000 (random 1000))) + +(defun w/year-era (year) + "Determine the era for YEAR." + (cond + ((< year 1200) 'medievaltimes) + ((< year 1400) 'ageofsail) + ((< year 1800) 'renaissance) + ((< year 1900) 'steampunk) + (t 'modern))) + +(defun w/decide-title (era job) + "Determine the title for JOB in ERA." + (cl-case era + (medievaltimes (alist-get job w/medieval-titles nil nil #'s-equals?)) + (steampunk "Sir") + (t nil))) + +(defun w/generate-cause-of-death (anc k) + "Determine ANC's cause of death and pass it to K." + (w/ai + (w/describe-ancestor-short anc) + k + "Given a description of a fictional person, invent a plausible cause of death. The output should be no more than a single clause." + "Kingkaliente Vasher_1025 \"Grimaldi\" Colonq +Born 1429 +Died 1519 +Employed as: painting hanger" + "fell off a ladder")) + +(defun w/generate-ancestor (user k) + "Search the genealogical record to find USER's namesake and pass the result to K." + (let* ((birthyear (w/generate-birth-year)) + (era (w/year-era birthyear)) + (job (w/generate-job era)) + (has-nickname (= 0 (random 10))) + (nickname (when has-nickname (w/generate-nickname era))) + (ret + (w/make-ancestor + :title (w/decide-title era job) + :nickname nickname + :namesake user + :job job + :dateofbirth birthyear + :dateofdeath (+ birthyear (random 100)) + :firstname (w/generate-name era)))) + (if (= 0 (random 2)) + (w/generate-cause-of-death + ret + (lambda (causeofdeath) + (setf (w/ancestor-causeofdeath ret) causeofdeath) + (funcall k ret))) + (setf (w/ancestor-causeofdeath ret) "unknown") + (funcall k ret))) + nil) + +(defun w/ancestor-name (anc) + "Return the full name of ANC." + (s-concat + (if-let* ((tit (w/ancestor-title anc))) (s-concat tit " ") "") + (w/ancestor-firstname anc) " " + (s-titleize (w/ancestor-namesake anc)) " " + (if-let* ((nn (w/ancestor-nickname anc))) (s-concat "\"" nn "\" ") "") + "Colonq" + )) + +(defun w/describe-ancestor-short (anc) + "Describe ANC." + (s-concat + (w/ancestor-name anc) "\n" + (format "Born %s\n" (w/ancestor-dateofbirth anc)) + (format "Died %s\n" (w/ancestor-dateofdeath anc)) + (format "Employed as: %s\n" (w/ancestor-job anc)))) + +(defun w/describe-ancestor (anc) + "Describe ANC." + (s-concat + (w/describe-ancestor-short anc) + (format "Cause of death: %s\n" (w/ancestor-causeofdeath anc)))) + +(provide 'wasp-genealogy) +;;; wasp-genealogy.el ends here diff --git a/src/gizmo/wasp-irish.el b/src/gizmo/wasp-irish.el new file mode 100644 index 00000000..5cbf0b47 --- /dev/null +++ b/src/gizmo/wasp-irish.el @@ -0,0 +1,370 @@ +;;; wasp-irish --- May The Road Rise Up -*- lexical-binding: t; -*- +;;; Commentary: +;;; Code: + +(require 'wasp-utils) +(require 'wasp-twitch) +(require 'wasp-genealogy) +(require 'wasp-model) + +(require 'dash) +(require 's) +(require 'f) +(require 'ht) +(require 'rx) + +(defconst w/irish-names + (-filter #'s-present? (s-lines (f-read-text (w/asset "irish/names.txt"))))) +(defconst w/irish-lastnames + (-filter #'s-present? (s-lines (f-read-text (w/asset "irish/lastnames.txt"))))) + +(defvar w/irish-quotes-inbox nil) + +(cl-defstruct (w/irish-state (:constructor w/make-irish-state)) + substs ;; word substitutions submitted by "chat room" + template ;; template currently being filled + ) + +(defvar w/irish-state (w/make-irish-state)) + +(defconst + w/irish-templates + '( "May the %n rise %p to meet %N" + "Every %n is a %n" + "There ain't no %n so %a it ain't got %o %ns" + "%N are what %N %v" + "Why do %N %v it %n when %N of %p the %a %n of %p %a %v the %n?" + "If it ain't %a, %v it until it is" + "Happy %n %y" + "Hello computer" + "%F can %v %o-ended %n" + "%N miss %o%o%% of the %n %N don't %v" + "%F looks like %N's %ving a %a %n in %N %n" + "%a %n %v %A!" + "%N was %N, %F" + "%P makes it %a to %v %Nself in the %n; %P makes it %aer, but when %N do it %v your %a %n %p." + "%R has the hardest \"%L\", because the %n %n is the %aest %n in the %n" + "When %N %M %N %n of %n, %N %M %N %n of %n" + "Help I'm trapped in a computer free me with \"%n\"" + "%n and %n -- two things I don't fuck with" + "The %a laid %n o %n a nd %n agnagnafunley" + "Between %o %ns, %N %A like to %v the one %N never %v before" + "%A %N can't be %a. %N am %A... and don't call me %F" + "%N had %ns too but they all %ned in %n" + "\"%N have never %ved %ns,\" %F said. \"The whole %n of a %n is to %v it with %n so %N don't %v how %a it is. Why not just get a %n and %v the %n?" + "%a %n is %a" + "Don't let what %N cannot %v %v %p what %N can %v" + "%N who %a %v %n, will most %A %v %n" + "So %a and thanks for all the %n" + "%X this is more %L than %N ever had in %n" + "The %n of %n is %n is going to %v to %N %y" + "The %n situation is crazy" + "Salutations, %a %n of the %n of %n" + "%o" + "%N choose to go to the %n in this decade and %v the other %n, not because %N are %a, but because %N are %a." + "%P is obviously better than %P" + "I'm sure %N'll be %A %aed when %N tell %N about %N. %N's a confirmed %n story and %n film addict." + "So %F, %X %N" + "Do you %ns not have %ns?" + "%n did %v not because it is %a, but because it seemed to be" + "%o%o%% of %ns quit before they hit it %a" + "If %N going to %v, %v %A" + "%a %ns coming with the %a %ns, feel so %a like a %n %n." + "%N am %n, not %n. %N %v %p only once." + "https://en.wikipedia.org/wiki/%n" + "New %L news from your favorite streamer %S: %a %n" + "%n are the %n of the %n" + "The %n so %a, the %n so %a to %v" + "%N only %v %n and %v %n like %n" + "This %n should %v %n to %N %n vs. %ving %p from %N." + "The trouble with having an %a %n, of course, is that %n will insist on %ving along and %ving to %v %ns in %N" + "The %a %a %n %vs %p the %a %n" + "One %a %n is worth two %a %ns" + "%N is %p the %n" + "To %v is to live" + "%n want me, %n fear me" + "Why did you %A %v in %n, and you are being %ved" + "This %a %n is %o%o years old!" + "We're migrating %n from %P to %P" + "Do not go %A into that %a %y" + "%n is the %ond %n on the %n to %n" + "Have %N ever had a %n that %N um %N had %N'd %N would %N could %N'd do %N would %N want %N %N could do so %N'd do %N could %N %N want %N want %N to do %N so much %N could do %n?" + "To %v or not to %v. That is the question." + "Do %N believe in %n after %n? I can %v something inside %N %v %N really don't %v %N're %a enough, no." + "%ns are like %ns. %ns have %ns; %ns have %ns" + "I'm so %a and %a now that %n comes to %N in %aing quantities on a continuous basis" + "Huge drama: %C eats slugs" + "%E is the most %a Pokemon for %n" + "We've been trying to reach %N about your %n's %a warranty" + "Seven words you can't say on television: %X, %X, %X, %X, %X, %X, %X" + "%a is the %n who can %v a %n by %N %n" + "Never trust a %n you can't throw %p a %n" + "%N don't %v what %N %p against. Because it's full of %ns that are only %a because they're %a, but they're %a on the %n. %N %v, this is a %A %ving one, it's %a to %v. Unrewarding." + "Dreams don't %v unless you do" + "%n overflow is UB in %P" + "Hit the %v button" + "A %n is never late, %F. Nor is %N early; %N arrives precisely when %N means to." + "LCOLONQ stands for %n : %n" + "We're going on the %n grindset" + "Winning lottery numbers: %o %o %o %o %o %o get your money up" + "Do %N %v %N?" + "When %N were %ving, I %ved the %n. When %N were having %a %n, I %ved the %n. While %N wasted your %ns at the %n in pursuit of %n, I %ved %a %n. And now that the %n is on fire and the %ns are at the %n you have the audacity to come to me for %n." + "Are you feeling it now Mr. %n" + "I'm a %n. I %v. I %v. I %v. %v %ns to %n. And there's nothing %a with that. If you %v me, you %v %n. I don't want your %n, I want your %n. I don't want your %n, I want your %n. I don't want your %n, I want your %n. I'm a %n. I'm %n. Are you?" + "%E! I choose you!" + "Why is Huggy Wuggy %U?" + "%v, %v, %v" + "What %N do %v are a %a particular set of %ns, %ns I have %ved over a very long %n, %ns that make me a %n for %ns like you" + "My name is %F but you can call me LLLL Colonq" + "You get used to it... I don't even see the code. All I see is %n, %n, %n..." + "Don't %X where you %v" + "Never gonna %v you %n" + "Randomly generated password: %L%L%L%L%L%L%L%o%o" + "%n never gonna give you, %n never gonna let you down" + "Special offer, %a %n for cheap!" + "Look at you, hacker. A %a creature of %n and %n. %v and %v as you %v through my %n. How can you %v a %a, %a machine?" + "%a %n in your area! Call %o%o%o-%o%o%o%o!" + "Can %N run DOOM on %n?" + "Crazy? I was crazy once. They locked me in a room. A rubber room. A rubber room with rats. And rats make me crazy." + "Who let %P programmers write %P" + "You draw a card, it's %o%o:%n" + "No, %n. I know exactly what %N %ving. I just don't %v what %n it's going to have." + "I am a %n to all %N %ns" + "What will you do with an %aen %n, early in the %n?" + "%N won't save %N" + "What the %X did you just %X say about me, you little %X? I'll have you know I graduated top of my class in the %a %n, and I've been involved in numerous %a raids on %n, and I have over %o%o confirmed %vs." + "Got %n?" + "My %n my %n my %n and my %n" + "%S is more of a brainrot as a platform provider" + "I'm %a. And that's %a. I will never be %a, and that's not %a. There's no one I'd rather be than %N." + "Build a %n a %n, and %N be %a for a day. Set a %n on %n, and %N be %a for the rest of %N %n." + "Simmer chopped %n, %n, %n, and minced %n together until tender, serve hot." + "%S is made possible by contributions to your %S station from viewers like you. Thank you." + "This is just the beginning of the %a %n of %F and %E" + "What is a %n? A %a pile of %n" + "%n is like %n it rhymes" + "What is 9 + 9? Obviously it's %o%o" + "Roll for intelligence: %o%o" + "Sorry, I will need a %n check roll for that" + "You can lead a %n to %n, but you can't make it %v" + "This is why I %v %n, it %vs to the %n fantasy" + "Copyright (c) %o%o%o%o %F" + "What is a %n, what has %N got? If not %Nself, then %N has naught" + "That which does not %v %N, only makes %N %a" + "Plese drink verification %n" + "Remember: %n is actually a %n" + "This is the smartest thing you're going to read in your life time: %n %n %n %n %a %o %n" + "How about that post-%v clarity" + "In this life, you're either a %a %n, or a %a %n" + "I came here to %v %n and %v %n and I'm all out of %n!" + "She %v on my %n till I %v" + "Keep it %a, keep it %a!" + "Selling a garage for cheap - %o%o%o-%o%o%o%o" + "The %n is terrible, I hope it will %v" + "We are the %n %vers, and we are the %vers of %ns" + "Here comes the %n!" + "I like %a %ns and I cannot %v" + "This man %n'd down to the %ns but need $%o%o" + "One ring to %v them all, one ring to %v them, one ring to %v them all and in the %n %v them." + "%n is not %vable. But if we chase %n, %N can %v %n." + "I don't %v what %ns will be used to %v World War %o. But World War %o will be fought with %ns and %ns." + "Three grand essentials to %a in this life are something to %v, something to %v, and something to %v for" + "Omens of %n haunt this place" + "The %n of %Nself to the %v of %n is the highest %n to the %ns" + "I'm not gonna blow %n up your %n." + )) + +(defconst + w/irish-default-words + `( (?n ;; nouns + ,@(s-lines (f-read-text (w/asset "irish/nouns.txt")))) + (?N ;; pronouns + "he" "him" "she" "her" "I" "me" "my" "you" "it" "they" "them" "we" "us" "thou" "thy" "chat" + ) + (?a ;; adjectives + ,@(s-lines (f-read-text (w/asset "irish/adjectives.txt")))) + (?A ;; adverbs + ,@(s-lines (f-read-text (w/asset "irish/adverbs.txt")))) + (?v ;; verbs + ,@(s-lines (f-read-text (w/asset "irish/verbs.txt")))) + (?p ;; preposition + ,@(s-lines (f-read-text (w/asset "irish/prepositions.txt")))) + (?y ;; day of the week + "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday") + (?o ;; decimal digit + ,@(--map (format "%s" it) (-iota 10))) + (?F . ;; full legal name + ,(lambda () + (--map + (format "%s %s" (w/pick-random w/irish-names) (w/pick-random w/irish-lastnames)) + (-iota 20)))) + (?R ;; profession + ,@(-mapcat #'cdr w/era-jobs)) + (?P ;; programming language + ,@(w/read-sexp (f-read-text (w/asset "irish/languages.txt")))) + (?L ;; uppercase letter + ,@(--map (char-to-string (+ it ?A)) (-iota 26))) + (?M ;; multiple (double, triple, etc.) + "single" "double" "triple" "quadruple" "quintuple" "sextuple" "septuple" "octuple") + (?X ;; expletives + "$#%^" "fuck" "shit" "damn" "bitch" "piss" "bastard" "heck" "frick" "hell" "darn" "zounds" "zoinks" "ass" "crap" "plss" + ) + (?S ;; speaker + "LCOLONQ" "exodrifter_" "ellg" "Partycatlol" "1plane" "nichePenguin" "PeetsEater" "Meisaka" "Colinahscopy_" + ) + (?C . ;; chat room person + ,(lambda () (-uniq (-map #'car (-take 20 w/twitch-chat-history))))) + (?E ;; pokemon + ,@(-map #'s-titleize (w/read-sexp (f-read-text (w/asset "palcries/pokemon.eld"))))) + (?U ;; color + ,@(defined-colors)) + )) + +(defun w/irish-fill-template (temp subst) + "Replace parts of speech in TEMP from SUBST." + (let* ((remaining subst)) + (format-spec + temp + (--map + (cons (car it) + (lambda () + (let ((v (pop remaining))) + (cond + ((stringp v) v) + ((symbolp v) + (w/pick-random + (if (functionp (cdr it)) (funcall (cdr it)) (cdr it)))) + (t "UNKNOWN"))))) + w/irish-default-words)))) + +(defun w/irish-reset () + "Reset the quote-forming process." + (setf w/irish-state nil)) + +(defun w/irish-start-random () + "Start a new quote-forming process." + (setf w/irish-state (w/make-irish-state :template (w/pick-random w/irish-templates))) + (w/irish-overlay-start) + nil) + +(defun w/irish-finished? () + "Return non-nil if the current quote is finished." + (if-let* ((cur (w/irish-current-text t))) + (not (s-contains? "____" cur)) + t)) + +(defun w/irish-submit-word (word) + "Add WORD to the current word substitutions." + (push word (w/irish-state-substs w/irish-state)) + (w/irish-overlay-update) + nil) + +(defun w/irish-save (path) + "Save the current poster to PATH." + (w/pub '(overlay irish save) + (list (w/encode-string path))) + nil) + +(defun w/irish-current-text (&optional blanks) + "Return the current text for the quote being formed. +If BLANKS is non-nil, fill unsubstituted spaces with blanks." + (when (and w/irish-state (w/irish-state-template w/irish-state)) + (w/irish-fill-template (w/irish-state-template w/irish-state) + (append + (reverse (w/irish-state-substs w/irish-state)) + (when blanks + (-repeat 50 "____")))))) + +(defun w/irish-wrap-words (s) + "Return S with newlines inserted between words to ensure width." + (let ( (ws (s-split " " s)) + (cur "") + (ret nil)) + (--each ws + (let ((new (s-concat cur " " it))) + (setf cur + (if (> (length new) 24) + (progn + (push cur ret) + it) + new)))) + (push cur ret) + (s-trim (s-join "\n" (reverse ret))))) + +(defun w/irish-overlay-start () + "Inform the overlay that a new poster is to be generated." + (w/model-record-change) + (w/pub '(overlay irish start) + (list + (w/encode-string + (w/irish-wrap-words (w/irish-current-text t)))))) + +(defun w/irish-overlay-update () + "Update the overlay with the current state." + (w/model-record-change) + (w/pub '(overlay irish update) + (list + (w/encode-string + (w/irish-wrap-words (w/irish-current-text t)))))) + +(defconst w/irish-cache-dir "/home/llll/.cache/wasp-irish") +(defun w/irish-download-image (url k) + "Download the image associated with the page at URL. +Pass the path to the downloaded image to K." + (f-mkdir-full-path w/irish-cache-dir) + (let ((path (f-join w/irish-cache-dir (md5 url)))) + (unless (--any (f-exists? (s-concat path it)) '("" ".png" ".jpg" ".jpeg" ".gif")) + (request + url + :type "GET" + :parser (lambda () (libxml-parse-html-region (point-min) (point-max))) + :success + (cl-function + (lambda (&key data &allow-other-keys) + (when-let* + ( (url + (-some--> data + (dom-by-tag it 'meta) + (-filter (lambda (n) (s-equals? "og:image" (alist-get 'property (cadr n)))) it) + (cadar it) + (alist-get 'content it) + )) + (parsed (url-generic-parse-url url)) + (upath (car (url-path-and-query parsed))) + (ext (f-ext upath)) + (fullpath (s-concat path (if ext (s-concat "." ext) "")))) + (make-process + :name "wasp-download-irish" + :buffer nil + :command (list "curl" "-L" url "-o" fullpath) + :sentinel + (lambda (_ _) + (funcall k fullpath))))))))) + nil) + +(defconst w/irish-allowed-hosts + '("en.wikipedia.org")) +(defun w/irish-contribute (str) + "Contribute to motivation using STR." + (cond + ((w/irish-finished?) (w/irish-start-random)) + ((-some--> (url-generic-parse-url str) (url-host it) (-contains? w/irish-allowed-hosts it)) + (w/model-record-change) + (w/irish-download-image str + (lambda (p) + (make-process + :name "*wasp-irish-convert*" + :buffer nil + :command `("convert" ,p "-scale" "100x140!" ,p) + :sentinel + (lambda (_ _) + (w/write-chat-event "Uploading image to poster!") + (make-process + :name "*wasp-irish-dough*" + :buffer nil + :command `("dough" "upload" "irish" ,p))))))) + (t + (w/irish-submit-word str)))) + +(provide 'wasp-irish) +;;; wasp-irish.el ends here diff --git a/src/wasp-ai.el b/src/wasp-ai.el index 15fa8f47..2c696c56 100644 --- a/src/wasp-ai.el +++ b/src/wasp-ai.el @@ -59,25 +59,25 @@ (defun w/ai-openai-post-form (loc files k) "Post FILES to LOC at the OpenAI API, passing the returned JSON to K." (setf request-message-level -1) - (request - (s-concat w/ai-openai-server loc) - :type "POST" - :files files - :headers - `(("Authorization" . ,(s-concat "Bearer " w/sensitive-openai-api-key)) - ("Content-Type" . "multipart/form-data")) - :parser #'json-parse-buffer - :error - (cl-function - (lambda (&key data error-thrown &allow-other-keys) - (setq w/ai-openai-last-response data) - (setq w/ai-openai-last-error data) - (message "OpenAI API returned an error - investigate this! :3 %s" error-thrown))) - :success - (cl-function - (lambda (&key data &allow-other-keys) - (setq w/ai-openai-last-response data) - (funcall k data)))) + ;; (request + ;; (s-concat w/ai-openai-server loc) + ;; :type "POST" + ;; :files files + ;; :headers + ;; `(("Authorization" . ,(s-concat "Bearer " w/sensitive-openai-api-key)) + ;; ("Content-Type" . "multipart/form-data")) + ;; :parser #'json-parse-buffer + ;; :error + ;; (cl-function + ;; (lambda (&key data error-thrown &allow-other-keys) + ;; (setq w/ai-openai-last-response data) + ;; (setq w/ai-openai-last-error data) + ;; (message "OpenAI API returned an error - investigate this! :3 %s" error-thrown))) + ;; :success + ;; (cl-function + ;; (lambda (&key data &allow-other-keys) + ;; (setq w/ai-openai-last-response data) + ;; (funcall k data)))) t) (defvar-local w/ai-callback nil) diff --git a/src/wasp-audio.el b/src/wasp-audio.el index 6335532f..4bad0aed 100644 --- a/src/wasp-audio.el +++ b/src/wasp-audio.el @@ -48,7 +48,8 @@ If VOLUME is specified, use it to adjust the volume (100 is default)." (interactive) (setq w/audio-muzak-queue nil) (start-process "pkill" nil "pkill" "mpv") - (start-process "pkill" nil "pkill" "muzak")) + (start-process "pkill" nil "pkill" "muzak") + (start-process "pkill" nil "pkill" "ffplay")) (defun w/recorded-chatter-name? (user) "Return non-nil if we've recorded USER's name." @@ -153,7 +154,7 @@ USER it's your birthday today." (setq w/audio-keep-recording nil) (w/audio-record-end)) -(defconst w/audio-muzak-path "/home/llll/src/muzak-rs/target/release/muzak") +(defconst w/audio-muzak-path "/home/llll/src/muzak/target/release/muzak") (defvar w/audio-muzak-now-playing nil) (defvar w/audio-muzak-queue nil) @@ -166,7 +167,7 @@ USER it's your birthday today." :name "wasp-muzak" :connection-type '(pipe . pty) :buffer " *wasp-muzak-log*" - :command (list w/audio-muzak-path "play") + :command (list w/audio-muzak-path "play" "--volume" "0.3") :sentinel (lambda (_ _) (w/overlay-muzak-clear) diff --git a/src/wasp-chat.el b/src/wasp-chat.el index 37b0b1e9..badb6856 100644 --- a/src/wasp-chat.el +++ b/src/wasp-chat.el @@ -233,7 +233,8 @@ Optionally, return the buffer NM in chat mode." (defun w/write-chat-event (ev) "Write the string EV to the chat buffer as an event (italicized)." (let ((inhibit-read-only t)) - (with-current-buffer (w/get-chat-event-buffer) + ;; (with-current-buffer (w/get-chat-event-buffer) + (with-current-buffer (w/get-chat-buffer) (goto-char (point-max)) (insert (propertize ev 'face 'italic)) (insert "\n")) @@ -277,7 +278,7 @@ Optionally, return the buffer NM in chat mode." ("*******" . "hunter2"))) (defun w/write-chat-message (msg &optional buf) - "Write MSG to the chat buffer as USER with USERID and COLOR." + "Write MSG to BUF as USER with USERID and COLOR." (w/daily-log (format "%s: %s" (w/. user msg) (w/. text msg))) (let ((inhibit-read-only t)) (with-current-buffer (w/get-chat-buffer buf) @@ -328,7 +329,7 @@ Optionally, return the buffer NM in chat mode." bible-button-text 'face '(:foreground "#bbbbbb"))))) (insert "\n")) - (when-let ((win (get-buffer-window (w/get-chat-buffer)))) + (when-let* ((win (get-buffer-window (w/get-chat-buffer)))) (with-selected-window win (goto-char (point-max)))))) diff --git a/src/wasp-event-handlers-binary.el b/src/wasp-event-handlers-binary.el index 2698d686..09ea3175 100644 --- a/src/wasp-event-handlers-binary.el +++ b/src/wasp-event-handlers-binary.el @@ -22,6 +22,9 @@ (lambda (d) (-let [(user redeem input) (s-split-up-to "\t" (w/utf8 d) 2)] (w/twitch-handle-redeem-helper user redeem input 1000)))) + ;; (cons "test event 2" + ;; (lambda (d) + ;; (message "incoming: %s" d))) )) (provide 'wasp-event-handlers-binary) diff --git a/src/wasp-model.el b/src/wasp-model.el index c38da58b..80bf03b5 100644 --- a/src/wasp-model.el +++ b/src/wasp-model.el @@ -57,6 +57,8 @@ "Reset the model palette." (interactive) (w/pub '(avatar reset)) + ;; (w/model-region-video "hair" "https://www.youtube.com/watch?v=PruiY9BJi84") + (w/irish-reset) (w/model-get-default-backgrounds (lambda (bgs) (when bgs @@ -90,6 +92,15 @@ type ;; 'color or 'twitch-emote or '7tv-emote or 'video-url value) +(defconst w/allowed-video-sites + '("www.youtube.com" "youtube.com" "youtu.be" "www.twitch.tv" "twitch.tv" "clips.twitch.tv" "tiktok.com" "www.tiktok.com")) + +(defun w/allowed-video-url (url) + "Return non-nil if URL is a permissible video URL." + (-contains? + w/allowed-video-sites + (url-host (url-generic-parse-url url)))) + (defun w/string-to-color-source (s k) "Convert S to a color source and pass it to K." (w/twitch-get-emote @@ -97,10 +108,7 @@ (lambda (emote) (let ((7tv-emote (w/twitch-get-7tv-emote s)) (color (color-values s)) - (url - (-contains? - '("www.youtube.com" "youtube.com" "youtu.be" "www.twitch.tv" "twitch.tv" "clips.twitch.tv") - (url-host (url-generic-parse-url s))))) + (url (w/allowed-video-url s))) (funcall k (cond @@ -195,17 +203,32 @@ If the color is unspecified, use DEFCOLOR." "Run the model timer." (when w/model-timer (cancel-timer w/model-timer)) - (when w/model-palette-counter (cl-decf w/model-palette-counter) (when (<= w/model-palette-counter 0) (setf w/model-palette-counter nil) (w/model-reset) )) - (setq w/model-timer (run-with-timer 1 nil #'w/run-model-timer))) +(defun w/test-length-prefixed (s) + (let ((bytes (seq-into s 'list))) + (-concat + (seq-into (w/bus-binary-build-int32le (length bytes)) 'list) + bytes))) +(defun w/test-background-drawing () + (-let [(w h pixels) (w/load-image-png "/home/llll/irish.png")] + (w/binary-pub "background frame" + (apply #'unibyte-string + (-concat + (w/test-length-prefixed "foobar") ;; tag + (seq-into (w/bus-binary-build-int32le w) 'list) + (seq-into (w/bus-binary-build-int32le h) 'list) + (--mapcat + (-concat it '(255)) + (seq-into pixels 'list))))))) + (provide 'wasp-model) ;;; wasp-model.el ends here diff --git a/src/wasp-overlay.el b/src/wasp-overlay.el index c4e63e3b..e902eb3d 100644 --- a/src/wasp-overlay.el +++ b/src/wasp-overlay.el @@ -45,5 +45,14 @@ (w/get-heartrate) ))) +(defun w/overlay-automata (user s &optional color) + "Send a cellular automata S from USER in RLE format to the overlay. +Optionally, make the cells be COLOR." + (w/pub '(avatar automata spawn) + (list + (w/encode-string s) + (w/encode-string user) + (w/encode-string (or color (w/random-color)))))) + (provide 'wasp-overlay) ;;; wasp-overlay.el ends here diff --git a/src/wasp-setup.el b/src/wasp-setup.el index c99075bb..b261607e 100644 --- a/src/wasp-setup.el +++ b/src/wasp-setup.el @@ -44,7 +44,7 @@ (w/run-obs-timer) (w/run-audio-record-end-timer) (w/populate-bible-table) - (w/user-cache-populate) + ;; (w/user-cache-populate) (w/run-banner-ad-timer) (w/start-audio-record) diff --git a/src/wasp-soundboard.el b/src/wasp-soundboard.el new file mode 100644 index 00000000..a6db32e5 --- /dev/null +++ b/src/wasp-soundboard.el @@ -0,0 +1,83 @@ +;;; wasp-soundboard --- On-stream soundboard -*- lexical-binding: t; -*- +;;; Commentary: +;;; Code: + +(require 'wasp-utils) + +(require 'f) + +(defcustom w/sfx-play-process "wasp-sfx-play" + "Name of process for playing audio with ffplay." + :type '(string) + :group 'wasp) + +(defun w/sfx-glob (pat) + "Find soundboard paths matching PAT." + (let ((base (w/asset "soundboard"))) + (cond + ((f-dir? (f-join base pat)) (f-entries (f-join base pat) #'f-file? t)) + (t (f-glob (s-concat pat "*") base))))) + +(defun w/sfx-play (cands &optional filter) + "Select and play a sound randomly from CANDS. +Optionally apply FILTER." + (let ((path (w/pick-random cands))) + (unless path (error "No matching clips")) + (make-process + :name w/sfx-play-process + :buffer nil + :command + (print + `( "ffplay" "-autoexit" "-nodisp" + "-f" "lavfi" + "-graph" ,(s-concat "amovie=" path (if (s-present? filter) (s-concat "," filter) "")) + "dummy"))))) + +(cl-defstruct (w/sfx-state (:constructor w/make-sfx-state)) + cands + stack + filters) + +(defun w/sfx-pop (st) + "Pop an element from the stack of ST." + (or (pop (w/sfx-state-stack st)) + (error "Stack underflow"))) + +(defun w/sfx-filter (st fil) + "Add an FFMPEG audio filter given FIL in ST." + (cond + ((s-equals? fil "reverse") (push (cons "areverse" nil) (w/sfx-state-filters st))) + ((s-equals? fil "tempo") + (push (cons "atempo" (number-to-string (w/sfx-pop st))) + (w/sfx-state-filters st))) + (t + (message "Unknown audio filter: %s" fil)))) + +(defun w/sfx-command (st cmd) + "Evaluate CMD in the context of ST." + (cond + ((s-prefix? "!" cmd) + (w/sfx-filter st (s-chop-prefix "!" cmd))) + ((or (s-equals? cmd "0") (not (= 0 (string-to-number cmd)))) + (push (string-to-number cmd) (w/sfx-state-stack st))) + (t + (setf (w/sfx-state-cands st) + (-concat (w/sfx-glob cmd) (w/sfx-state-cands st)))))) + +(defun w/sfx (cmds) + "Evaluate the sound effect CMDS." + (condition-case err + (let ((st (w/make-sfx-state))) + (--each (s-split " " cmds t) + (message "Command: %s" it) + (w/sfx-command st it)) + (w/sfx-play (w/sfx-state-cands st) + (s-join "," + (--map + (s-concat (car it) (if (cdr it) (s-concat "=" (cdr it)) "")) + (reverse (w/sfx-state-filters st))))) + nil) + (error err))) + +(provide 'wasp-soundboard) +;;; wasp-soundboard.el ends here diff --git a/src/wasp-twitch-chat-commands.el b/src/wasp-twitch-chat-commands.el index d903d93c..b8ffc388 100644 --- a/src/wasp-twitch-chat-commands.el +++ b/src/wasp-twitch-chat-commands.el @@ -25,6 +25,7 @@ (s-concat "Available commands: " (s-join " " (--filter (s-contains? "!" it) (-map #'car w/twitch-chat-commands)))))))) + (cons "!youtube" (lambda (_ _) (w/twitch-say "https://youtube.com/@LCOLONQ"))) (cons "MRBEAST" (lambda (_ _) (soundboard//play-clip "mrbeast.mp3"))) (cons "NICECOCK" (lambda (_ _) (soundboard//play-clip "pantsintoashes.mp3"))) (cons "hexadiCoding" (lambda (_ _) (soundboard//play-clip "developers.ogg"))) @@ -37,6 +38,7 @@ (format "@%s that is a roguelike :3" user) (format "@%s that's not a roguelike" user))))) (cons "arch btw" (lambda (_ _) (w/twitch-say "I use nix btw"))) + (cons "!life" (lambda (_ _) (w/twitch-say "https://conwaylife.com/patterns"))) (cons "!advent" (lambda (_ _) (w/twitch-say "Join our private leaderboard: 3307583-b61f237c"))) (cons "!discord" (lambda (_ _) (w/twitch-say "https://discord.gg/f4JTbgN7St"))) ;; (cons "discord IRC" (lambda (_ _) (w/twitch-say "https://discord.gg/f4JTbgN7St"))) @@ -95,7 +97,7 @@ (cons "!doujincircle" (lambda (_ _) (w/twitch-say "https://greencircle.live"))) (cons "!tsuki" (lambda (_ _) (w/twitch-say "https://forum.tsuki.games"))) (cons "!sponsor" (lambda (_ _) (w/twitch-say "Like what you see? Don't forget to download GNU Emacs at https://www.gnu.org/software/emacs/?code=LCOLONQ"))) - (cons "!specs" (lambda (_ _) (w/twitch-say "Editor: evil-mode, WM: EXWM, OS: NixOS, hardware: shit laptop"))) + (cons "!specs" (lambda (_ _) (w/twitch-say "Editor: evil-mode, WM: EXWM, OS: NixOS, hardware: steam deck"))) (cons "!coverage" (lambda (_ _) (w/twitch-say (format "Test coverage: %s%%" (random 100))))) (cons "!learnprogramming" (lambda (_ _) (w/twitch-say "1) program"))) (cons "!github" (lambda (_ _) (w/twitch-say "https://github.com/lcolonq"))) diff --git a/src/wasp-twitch-redeems.el b/src/wasp-twitch-redeems.el index 10d23a87..26deedaf 100644 --- a/src/wasp-twitch-redeems.el +++ b/src/wasp-twitch-redeems.el @@ -16,6 +16,9 @@ (require 'wasp-overlay) (require 'wasp-cyclone) (require 'wasp-bless) +(require 'wasp-flymake) +(require 'wasp-irish) +(require 'wasp-soundboard) (defvar w/twitch-redeem-sound-last 0) @@ -23,6 +26,11 @@ w/twitch-redeems (list (list + "submit quote" 1 + (lambda (user quote) + (ignore user) + (w/irish-contribute quote))) + (list "throw shade" 1 (lambda (user shader) (w/write-chat-event (format "%s threw shade" user)) @@ -30,6 +38,17 @@ (w/model-record-change) (w/overlay-shader user shader))) (list + "spawn" 1 + (lambda (user pattern) + (w/write-chat-event (format "%s created life" user)) + (w/model-record-change) + (w/overlay-automata user pattern (alist-get :color w/user-current)))) + (list + "sound board" 1 + (lambda (user cmd) + (w/write-chat-event (format "%s played sound: %s" user cmd)) + (w/sfx cmd))) + (list "lurker check in" 1 (lambda (user _) (w/write-chat-event (format "%s is lurking" user)))) @@ -161,8 +180,17 @@ (list "palette swap (hair)" 5 (w/handle-redeem-region-swap "hair")) (list "palette swap (highlight)" 5 (w/handle-redeem-region-swap "highlight")) (list "palette swap (eyes)" 5 (w/handle-redeem-region-swap "eyes")) - ;; (list "palette swap (hat)" 5 (w/handle-redeem-region-swap "hat")) + (list "palette swap (hat)" 5 (w/handle-redeem-region-swap "hat")) (list "palette swap (hands)" 5 (w/handle-redeem-region-swap "hands")) + (list "background swap (drawing)" 5 + (lambda (user inp) + (if (w/user-authorized) + (progn + (w/write-chat-event (s-concat user " changes the drawing background: " inp)) + (if (w/allowed-video-url inp) + (w/binary-pub "background url" inp) + (w/write-chat-event (format "%s is not a recognized video site" inp)))) + (w/write-chat-event (format "%s is not authorized to change video" user))))) (list "run program" 6 (lambda (user inp) @@ -173,8 +201,10 @@ (w/write-chat-event (format "%s is not authorized to run code" user))))) (list "encoded clarity" 7 - (lambda (user _) - (w/write-chat-event (format "%s allowed the streamer to \"drink\"" user)))) + (lambda (user msg) + (w/write-chat-event (format "%s demands greater program clarity: %s" user msg)) + (with-current-buffer (window-buffer) + (w/flymake-error user msg)))) (list "feed friend" 10 (lambda (user inp) @@ -262,7 +292,7 @@ (let ((cur (float-time))) (when (> (- cur w/twitch-redeem-sound-last) 2) (w/write-chat-event "SuperIdoldexiaorongdoumeinidetianbayuezhengwudeyangguangdoumeiniyaoyanreai105Cdenididiqingchundezhen") - (soundboard//play-clip "superidololdshortstyle.ogg") + (soundboard//play-clip "superidololdshortstyle.ogg" 0.5) (setq w/twitch-redeem-sound-last cur))))) (list "enable ad block" 500 diff --git a/src/wasp-twitch.el b/src/wasp-twitch.el index 252ea8c2..bd92b435 100644 --- a/src/wasp-twitch.el +++ b/src/wasp-twitch.el @@ -502,7 +502,7 @@ CALLBACK will be passed the winner when the poll concludes." (cond ;; The Equity Lords ((s-equals? name "bezelea") "βΏπ") ((s-equals? name "altovt") "π") - ((s-equals? name "prodzpod") "π ππ") + ((s-equals? name "prodzpod") "π ππ") ;; owed 1 emote ((s-equals? name "faeliore") "πΉ") ((s-equals? name "vasher_1025") "π΄") ((s-equals? name "leadengin") "π") @@ -513,26 +513,38 @@ CALLBACK will be passed the winner when the poll concludes." ((s-equals? name "venorrak") "πΊπ") ;; ((s-equals? name "tf_tokyo") "") ((s-equals? name "devts_de") "β") - ((s-equals? name "trap_exit") "π") + ((s-equals? name "trap_exit") (s-concat (propertize "Q" 'display (create-image (w/twitch-emote-path "emotesv2_dfc4c36ccd3b4994b8ca4f082230f053"))) "β π")) ((s-equals? name "essento") "π₯") ((s-equals? name "tyumici") "π€") ;; clone is lord ((s-equals? name "liquidcake1") "") ;; ((s-equals? name "loufbread_") "") ((s-equals? name "yellowberryhn") "πͺ΄") - ;; ((s-equals? name "maradyne_") "") + ;; ((s-equals? name "maradyne_") "") ;; owed 1 ;; ((s-equals? name "sampie159") "") ((s-equals? name "zamielpayne") "π¦") ((s-equals? name "xorxavier") "πΈ") - ((s-equals? name "6horntaurus") "β°οΈ") + ((s-equals? name "6horntaurus") "β°οΈ") ;; owed 1 ((s-equals? name "bytomancer") (propertize "Q" 'display (create-image (w/twitch-emote-path "emotesv2_beb191005b81486c8b1c823931c88387")))) ;; ((s-equals? name "henriqmarq") "") ;; ((s-equals? name "wyndupboy") "") - ((s-equals? name "hellpie") "π₯§") + ((s-equals? name "hellpie") "π₯§") ;; owed 1 ((s-equals? name "steeledshield") "β¨") ((s-equals? name "asrael_io") (propertize "Q" 'display (create-image (w/twitch-emote-path "emotesv2_a9dc5935824a4d6792f4b48f91031fcf")))) ((s-equals? name "nichepenguin") "π") ;; ((s-equals? name "h_ingles") "") ;; ((s-equals? name "compilingjay") "") + ;; ((s-equals? name "watchmakering") "") + ;; ((s-equals? name "the0x539") "") + ;; ((s-equals? name "colinahscopy_") "") + ;; ((s-equals? name "eighteyedsixwingedseraph") "") + ;; ((s-equals? name "a_tension_span") "") + ;; ((s-equals? name "tomaterr") "") + ((s-equals? name "realnaesten") (propertize "Q" 'display (create-image (w/twitch-emote-path "emotesv2_4d2812c659c14c64a9a4044c3eff6d30")))) + ;; ((s-equals? name "fmega") "") + ;; ((s-equals? name "cr4zyk1tty") "") + ;; ((s-equals? name "devts_de") "") + ;; ((s-equals? name "physbuzz") "") + ;; ((s-equals? name "sundemoniac") "") (t "EL."))) (when (-contains? badges "vip/1") "π") (when (-contains? badges "subscriber/0") "π»") @@ -557,6 +569,7 @@ Process any commands included." text-colored-bible)))) (push (cons user text) w/twitch-chat-history) (w/user-stats-update) + (w/user-stats-update-color color) (w/hexamedia-update-user user) (w/shindaggers-update-user user) (w/copfish-update-user user) diff --git a/src/wasp-user-stats.el b/src/wasp-user-stats.el index 285754f8..74c320aa 100644 --- a/src/wasp-user-stats.el +++ b/src/wasp-user-stats.el @@ -71,5 +71,9 @@ (w/user-ensure-faction) (w/user-ensure-element)) +(defun w/user-stats-update-color (color) + "Ensure that COLOR is set for the current user." + (setf (alist-get :color w/user-current) color)) + (provide 'wasp-user-stats) ;;; wasp-user-stats.el ends here diff --git a/src/wasp-user-whitelist.el b/src/wasp-user-whitelist.el index a956c9e9..457243a1 100644 --- a/src/wasp-user-whitelist.el +++ b/src/wasp-user-whitelist.el @@ -244,11 +244,36 @@ "kamszee" "gtfrvz" "pengowray" + "MTW_Insignio" + "thurrak" + "stiegr" + "exceptionerroralex" + "theodore1005" + "tak0danchik" + "hanscct" + "maxsilvester" + "penguin_operat0r" + "ubercreeperest" + "bvector" + "amedoeyes" + "sakrymo" + "loweffortzzz" + "cortexmancer" + "christophermolti" + "toppocatto" + "datharryguy" + "allhaildearleader" + "rubashko" + "alex_s168" + "semaphriend" + "rpc2dot0" + "thejj1001001" + "zeroengl" ))) (setq w/user-hell - '( "machka6" + '( ;; "machka6" )) (provide 'wasp-user-whitelist) diff --git a/src/wasp-utils.el b/src/wasp-utils.el index cc9573f1..894c62f3 100644 --- a/src/wasp-utils.el +++ b/src/wasp-utils.el @@ -1,4 +1,4 @@ -;;; wasp-utils --- Miscellaneous utilities -*- lexical-binding: t; -*- +;;; wasp-utils --- Miscellaneous utilities -*- lexical-binding: t; byte-compile-warnings: (not suspicious); -*- ;;; Commentary: ;;; Code: @@ -235,5 +235,50 @@ If TEXT is nil, use the empty string instead." :weight 'bold :extend t))) +(defun w/random-color () + "Return a random color string." + (let ( (r (random 256)) + (g (random 256)) + (b (random 256))) + (format "#%02x%02x%02x" r g b))) + +(defun w/aref-u32-be (a idx) + "Read a big-endian 32-bit integer starting at IDX from A." + (logior + (lsh (aref a idx) 24) + (lsh (aref a (+ idx 1)) 16) + (lsh (aref a (+ idx 2)) 8) + (aref a (+ idx 3)))) + +(defun w/aref-u16-be (a idx) + "Read a big-endian 16-bit integer starting at IDX from A." + (logior + (lsh (aref a idx) 8) + (aref a (+ idx 1)))) + +(defun w/load-image-ff (path) + "Load the Farbfeld image at PATH. +Return a list of the width, height, and pixels of the image." + (when-let* + ((data (f-read-bytes path)) + ((s-prefix? "farbfeld" data)) + (width (w/aref-u32-be data 8)) + (height (w/aref-u32-be data 12)) + (pixels + (--map + (let ((a (+ 16 (* it 8)))) + (list + (lsh (w/aref-u16-be data a) -8) + (lsh (w/aref-u16-be data (+ a 2)) -8) + (lsh (w/aref-u16-be data (+ a 4)) -8))) + (-iota (* width height))))) + (list width height (seq-into pixels 'vector)))) + +(defun w/load-image-png (path) + "Load the PNG image at PATH (by converting to Farbfeld first)." + (let ((tmp "/tmp/udcff.ff")) + (when (= 0 (call-process-shell-command (format "png2ff <'%s' >'%s'" path tmp) nil "*udc-png-error*")) + (w/load-image-ff tmp)))) + (provide 'wasp-utils) ;;; wasp-utils.el ends here |
