summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gizmo/wasp-flycheck.el17
-rw-r--r--src/gizmo/wasp-flymake.el63
-rw-r--r--src/gizmo/wasp-friend.el4
-rw-r--r--src/gizmo/wasp-genealogy.el139
-rw-r--r--src/gizmo/wasp-irish.el370
-rw-r--r--src/wasp-ai.el38
-rw-r--r--src/wasp-audio.el7
-rw-r--r--src/wasp-chat.el7
-rw-r--r--src/wasp-event-handlers-binary.el3
-rw-r--r--src/wasp-model.el35
-rw-r--r--src/wasp-overlay.el9
-rw-r--r--src/wasp-setup.el2
-rw-r--r--src/wasp-soundboard.el83
-rw-r--r--src/wasp-twitch-chat-commands.el4
-rw-r--r--src/wasp-twitch-redeems.el38
-rw-r--r--src/wasp-twitch.el23
-rw-r--r--src/wasp-user-stats.el4
-rw-r--r--src/wasp-user-whitelist.el27
-rw-r--r--src/wasp-utils.el47
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