diff options
| author | LLLL Colonq <llll@colonq> | 2024-03-26 23:34:28 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2024-03-26 23:34:28 -0400 |
| commit | 782c667e824d426b5443591afeefc37d0ae17785 (patch) | |
| tree | ae5d232d598e2008bc2cadf32157a4d937b01951 /src/gizmo/wasp-friend.el | |
| parent | 8e9db9303fc5d72ddfdc9ab4a9adaa8299e6e21a (diff) | |
We streamed for 9 hours and (mostly) fixed everything.
Diffstat (limited to 'src/gizmo/wasp-friend.el')
| -rw-r--r-- | src/gizmo/wasp-friend.el | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/src/gizmo/wasp-friend.el b/src/gizmo/wasp-friend.el new file mode 100644 index 00000000..bdbf0818 --- /dev/null +++ b/src/gizmo/wasp-friend.el @@ -0,0 +1,580 @@ +;;; wasp-friend --- "friend" -*- lexical-binding: t; -*- +;;; Commentary: +;;; Code: + +(require 'dash) +(require 's) +(require 'flycheck) +(require 'wasp-utils) +(require 'wasp-audio) +(require 'wasp-ai) +(require 'wasp-chat) +(require 'wasp-twitch) +(require 'wasp-newspaper) + +(defcustom w/friend-buffer "*wasp-friend*" + "Name of buffer used to display \"friend\"." + :type '(string) + :group 'wasp) + +(define-derived-mode w/friend-mode special-mode "\"friend\"'s lair" + "Major mode for displaying \"friend\"'s lair." + :group 'wasp + (message "hi i'm \"friend\"") + (setq-local cursor-type nil)) + +(defun w/get-friend-buffer () + "Return the \"friend\" buffer." + (unless (get-buffer w/friend-buffer) + (with-current-buffer (get-buffer-create w/friend-buffer) + (w/friend-mode))) + (get-buffer w/friend-buffer)) + +(defun w/friend-journalism-input () + "Collect an input for \"friend\"'s journalism based on recent activities." + (s-join + "\n" + (cons + (format "LCOLONQ: %s" (s-trim w/last-stream-transcription)) + (--map + (format "%s: %s" (car it) (cdr it)) + (reverse (-take 5 w/twitch-chat-history)))))) + +(defun w/friend-journalism (author headline) + "Retrieve \"friend\"'s opinion on current events related to HEADLINE. +AUTHOR was a contributing author btw." + (w/ai + (s-concat + "Headline: " headline "\n\n" + (w/friend-journalism-input)) + (lambda (resp) + (when resp + (w/write-chat-event (format "\"friend\" finished writing about: %s" headline)) + (push + (w/make-newspaper-article + :headline headline + :author (format "\"friend\" and %s" author) + :content (s-trim resp)) + w/newspaper-todays-articles))) + "You are the personality of a desktop buddy named \"friend\". \"friend\" is irreverant but kind, and only speaks in lowercase. You are kind of dumb in a cute way and silly like a virtual pet. You live in the corner of LCOLONQ's stream and provide commentary on events. You like people, video games, emojis, learning, and food. Given a headline of a newspaper article and a summary of recent user activity, please do your best journalist impression and produce a one paragraph article about the situation that fits the headline." + )) + +(defconst w/friend-grapheme-phonemes + '((("b" "bb") . "bug") (("d" "dd" "ed") . "dad") + (("f" "ff" "ph" "gh" "lf" "ft") . "fat") + (("g" "gg" "gh" "gu" "gue") . "gun") (("h" "wh") . "hop") + (("j" "ge" "g" "dge" "di" "gg") . "jam") + (("k" "c" "ch" "cc" "lk" "qu" "q" "ck" "x") . "kit") + (("l" "ll") . "live") (("m" "mm" "mb" "mn" "lm") . "man") + (("n" "nn" "kn" "gn" "pn" "mn") . "net") (("p" "pp") . "pin") + (("r" "rr" "wr" "rh") . "run") + (("s" "ss" "c" "sc" "ps" "st" "ce" "se") . "sit") + (("t" "tt" "th" "ed") . "tip") (("v" "f" "ph" "ve") . "vine") + (("w" "wh" "u" "o") . "wit") + (("z" "zz" "s" "ss" "x" "ze" "se") . "zed") + (("s" "si" "z") . "treasure") (("ch" "tch" "tu" "te") . "chip") + (("sh" "ce" "s" "ci" "si" "ch" "sci" "ti") . "sham") + (("th ") . "thongs") (("th") . "leather") + (("ng" "n" "ngue") . "ring") (("y" "i" "j") . "you") + (("a" "ai" "au") . "cat") + (("a" "ai" "eigh" "aigh" "ay" "er" "et" "ei" "au" "ea" "ey") . "bay") + (("e" "ea" "u" "ie" "ai" "a" "eo" "ei" "ae") . "end") + (("e" "ee" "ea" "y" "ey" "oe" "ie" "i" "ei" "eo" "ay") . "be") + (("i" "e" "o" "u" "ui" "y" "ie") . "it") + (("i" "y" "igh" "ie" "uy" "ye" "ai" "is" "eigh") . "spider") + (("a" "ho" "au" "aw" "ough") . "swan") + (("o" "oa" "oe" "ow" "ough" "eau" "oo" "ew") . "open") + (("o" "oo" "u" "ou") . "wolf") (("u" "o" "oo" "ou") . "lug") + (("o" "oo" "ew" "ue" "oe" "ough" "ui" "oew" "ou") . "who") + (("oi" "oy" "uoy") . "join") (("ow" "ou" "ough") . "now") + (("a" "er" "i" "ar" "our" "ur") . "about") + (("air" "are" "ear" "ere" "eir" "ayer") . "chair") (("a") . "arm ") + (("ir" "er" "ur" "ear" "or" "our" "yr") . "bird") + (("aw" "a" "or" "oor" "ore" "oar" "our" "augh" "ar" "ough" "au") . "paw") + (("ear" "eer" "ere" "ier") . "ear") (("ure" "our") . "cure"))) + +(defconst w/friend-phonemes + (-sort + (-on #'> (lambda (x) (length (car x)))) + (--mapcat + (-map (lambda (g) (cons g (cdr it))) (car it)) + w/friend-grapheme-phonemes))) + +(defun w/friend-replace-graphemes (str) + "Replace all graphemes with phoneme words in STR." + (let* ((phoneme-codes (--map-indexed (cons (cdr it) (format "%s," it-index)) w/friend-grapheme-phonemes)) + (grapheme-codes (--map (cons (car it) (alist-get (cdr it) phoneme-codes nil nil #'s-equals?)) w/friend-phonemes)) + (cleaned (s-downcase (replace-regexp-in-string "[^[:alpha:]]" "" str)))) + (--map (car (nth (string-to-number it) phoneme-codes)) (-filter #'s-present? (s-split "," (s-replace-all grapheme-codes cleaned)))))) + +(defun w/friend-phoneme-path (ph) + "Return a randomly chosen path to the given PH." + (let ((samples (f--entries (w/asset "friendvoice/") (s-contains? ph it) t))) + (nth (random (length samples)) samples))) +(defun w/friend-pronounce-phonemes (ph) + "Say PH." + (let ((files (-map #'w/friend-phoneme-path ph))) + (apply + #'start-process + "phoneme-say" nil "playphonemes" + files))) + +;; (defun w/get-friend-expensive-tastes (k) +;; "Pass non-nil to K if \"friend\" has expensive tastes this stream. +;; Also update the cached Amazon stock price for next stream." +;; (fig//load-db2-entry +;; "LCOLONQ" :amzn-price +;; (lambda (price) +;; (let ((prev (or price 0)) +;; (cur (fig//stock-price "AMZN"))) +;; (fig//update-db-number "LCOLONQ" :amzn-price (lambda (_) cur)) +;; (funcall k (> cur prev)))))) + +(defvar w/friend-tastes " You love eating ectoplasm and blood and stuff and assorted other spooky things because you are currently a ghost.") +;; (fig//get-friend-expensive-tastes +;; (lambda (expensive) +;; (let ((moon (car (lunar-phase-for-date (calendar-current-date))))) +;; (setf +;; fig//friend-tastes +;; (s-concat +;; (cond +;; ((-contains? '("New" "Waxing Crescent") moon) " You prefer warm foods like soups.") +;; ((-contains? '("First Quarter" "Waxing Gibbous") moon) " You prefer to eat leafy greens and fruits.") +;; ((-contains? '("Full" "Waning Gibbous") moon) " You prefer to eat barbeque and grilled meats.") +;; ((-contains? '("Last Quarter" "Waning Crescent") moon) " You prefer to eat corn beans and squash.") +;; (t "") +;; ) +;; (if expensive " You have expensive taste in food and dislike any food that can be obtained cheaply." "")))))) + +;; states: +;; default +;; jumping +;; eating, eating0, eating1, eating2 +;; chatting, chatting0 +(defvar w/friend-state 'default) +(defvar w/friend-emotion "neutral") +(defvar w/friend-message-cache nil) +(defvar w/friend-state-timer 0) + +(defvar w/friend-animation 1) +(defvar w/friend-speech "") +(defvar w/friend-speech-timer 0) + +(defconst w/friend-composition-examples + '(("My Life Is Like A Video Game" . "A/A/c/c/c/dcc/c///a/a/a/f/g/f/f///a/a/a/a/g/g/ga//f//") + ("Super Idol" . "gg[g#]gfg[CD#cG#][D#][CG#f][Cd#][Cc]C[Cd#]/[DFfd][FA#][DA#f]D[Dg][A#f][Dd#a#]f[GBgd]B[Gd#][GDc][Gd#]G[Gd#]/[D#Gc]G[D#cg][D#g][D#g#][dg][D#f][d#d#][D#Ggc]f[D#][D#Gg][D#c][D#][D#c][d#][DFdA#]F[DA#d][Dd][Dg]/[Da#g]/[D#d#][D#][D#][D#][D#][FD#][GA#][fd#][gA#]") + ("Reindeer" . "FG/FD/B/A/G/////GAGAG/c/B///////FG/FD/B/A/G/////GAGAG/d/c/////|C4~~~G3~~~C4~~~G3~~~C~~~E3~D#3~D3~~~~~~~G3~~~D3~~~G3~~~D3~~~G3~~D3G3~B3/C4"))) + +(defun w/friend-compose-song (theme) + "Compose a song about THEME to play on the bells." + (w/ai + theme + (lambda (res) + (let* ((sp (s-split ":" (s-trim res))) + (name (s-trim (car sp))) + (song (s-trim (cadr sp)))) + (when (and (stringp name) (stringp song)) + (w/friend-respond + (format "You just composed a song about %s called %s! Say something about it!" theme name) + (lambda () + (w/write-chat-event (format "The song is called %s: %s" name song)) + (muzak//add-song (s-concat "friend's " name) song) + (muzak/play-tracks song)))))) + "Please compose a song about the provided theme. The format for the song is a sequence of characters with meanings as follows: / represents a rest, uppercase letters A through G indicate semitones, octaves are specified with a number following a semitone, ~ extends the duration of a note, square brackets like [] group notes together into a chord. The pipe character | separates tracks. Respond only with the song's name followed by a colon folowed by the song notes. Do not explain yourself. The song should ideally be 20 to 30 notes long." + (-map #'car w/friend-composition-examples) + (--map (format "%s: %s" (car it) (cdr it)) w/friend-composition-examples))) + +(defun w/friend-personality (msg k) + "Given MSG, pass a string with more personality to K." + (let ((call (s-concat w/friend-emotion " | " msg))) + (w/ai + call + (lambda (new) + (let ((sp (s-split "|" (s-trim new)))) + (if (= 2 (length sp)) + (progn + (when (stringp (car sp)) + (setf w/friend-emotion (s-trim (car sp)))) + (when (stringp (cadr sp)) + (let ((resp (s-trim (cadr sp)))) + (push (cons call (s-trim new)) w/friend-message-cache) + (funcall k resp)))) + (let ((resp (s-trim new))) + (push (cons call (s-trim new)) w/friend-message-cache) + (funcall k resp))))) + (s-concat + "You are the personality of a desktop buddy named \"friend\". \"friend\" is irreverant but kind, and only speaks in lowercase. You are kind of dumb in a cute way and silly like a virtual pet. You live in the corner of LCOLONQ's stream and provide commentary on events. Given an emotional state and a description of an event that happened to you, please respond with a new emotional state and a short message in response considering your emotional state. The message should only be one clause. You like people, video games, emojis, learning, and food." + "The theme of LCOLONQ's stream today is " (s-trim (w/slurp "~/today.txt")) " " + "The title of LCOLONQ's stream today is " w/twitch-current-stream-title " " + w/friend-tastes + ) + (cons "neutral | Mimeyu fed you an apple." (reverse (-take 5 (-map #'car w/friend-message-cache)))) + (cons "happy | yum apple so good" (reverse (-take 5 (-map #'cdr w/friend-message-cache)))) + ))) + +(defun w/enemy-personality (msg k) + "Given MSG, pass a string with more personality (enemy mode) to K." + (w/ai + (s-concat w/friend-emotion " | " msg) + (lambda (new) + (let ((sp (s-split "|" (s-trim new)))) + (when (= 2 (length sp)) + (when (stringp (car sp)) + (setf w/friend-emotion (s-trim (car sp)))) + (when (stringp (cadr sp)) + (funcall k (s-trim (cadr sp))))))) + (s-concat + "You are the personality of a desktop buddy named \"enemy\". \"enemy\" is irreverant and rude. You are very intelligent in a cute way and mean like a snake. You live in the corner of LCOLONQ's stream and provide commentary on events. Given an emotional state and a description of an event that happened to you, please respond with a new emotional state and a short message in response considering your emotional state. The message should only be one clause." + w/friend-tastes + ) + "neutral | notgeiser fed you bone hurting juice." + "disdainful | I really dislike you strongly, notgeiser." + )) + +(defun w/friend-set-state (st &optional time) + "Set \"friend\"'s state to ST for TIME seconds." + (setf w/friend-state st) + (setf w/friend-state-timer (or time 5))) + +(defun w/friend-set-speech (msg &optional time) + "Have \"friend\" say MSG for TIME." + (w/write-chat-event (s-concat "Friend says: " msg)) + (setf w/friend-speech msg) + (setf w/friend-speech-timer (or time 5))) + +(defun w/friend-say (msg) + "Have \"friend\" say MSG." + (w/friend-pronounce-phonemes (w/friend-replace-graphemes msg)) + (w/friend-set-speech msg 10) + (w/friend-set-state 'chatting 10)) + +(defun w/friend-feed (user food) + "Call when USER fed FOOD to \"friend\"." + (if (s-equals? "imgeiser" user) + (w/enemy-personality + (format "You dislike %s and they are your enemy. %s fed you %s" user user food) + (lambda (msg) + (w/friend-set-speech msg 6) + (w/friend-set-state 'eating 6))) + (w/friend-personality + (format "%s fed you %s" user food) + (lambda (msg) + (w/friend-set-speech msg 6) + (w/friend-set-state 'eating 6))))) + +(defun w/friend-respond (ev &optional k) + "Call when an event EV happens to \"friend\". +If K is specified, call it after the response." + (w/friend-personality + ev + (lambda (msg) + (w/friend-say msg) + (when k + (funcall k))))) + +(defun w/friend-chat (user msg) + "Call when USER sends MSG to \"friend\"." + (if (s-equals? user "imgeiser") + (w/enemy-personality + (format "You dislike %s and they are your enemy. %s says: %s" user user msg) + (lambda (msg) + (w/friend-set-speech msg 10) + (w/friend-set-state 'chatting 10))) + (w/friend-respond (format "%s says: %s" user msg)))) + +(defun w/friend-gift (user gift) + "Call when USER gave GIFT to \"friend\"." + (if (s-equals? user "imgeiser") + (w/enemy-personality + (format "You dislike %s and they are your enemy. %s gave you %s as a Christmas present." user user gift) + (lambda (msg) + (w/friend-set-speech msg 6))) + (w/friend-personality + (format "%s gave you %s as a Christmas present." user gift) + (lambda (msg) + (w/friend-set-speech msg 6))))) + +(defun w/friend-tfig (user tfig) + "Call when USER took TFIG from \"friend\"." + (if (not (s-equals? "imgeiser" user)) + (w/enemy-personality + (format "You dislike %s and they are your enemy. %s took away %s from you and stole your Christmas present." user user tfig) + (lambda (msg) + (w/friend-set-speech msg 6))) + (w/friend-personality + (format "%s took away %s from you and stole your Christmas present." user tfig) + (lambda (msg) + (w/friend-set-speech msg 6))))) + +;; (defun w/friend-react-wikipedia (user page) +;; "Call when USER asks \"friend\" to react to PAGE on Wikipedia." +;; (w/fetch-wikipedia +;; page +;; (lambda (sum) +;; (w/friend-respond (format "%s asks you to react to the Wikipedia page for %s. The page summary is: %s" user page sum))))) + +(defun w/callout-flycheck-error () + "Call to respond to a random Flycheck error in the current buffer." + (when-let* ((errs (--filter (eq (flycheck-error-level it) 'error) flycheck-current-errors)) + (err (nth (random (length errs)) errs))) + (w/friend-respond + (s-concat + "LCOLONQ made an error while programming: " + (flycheck-error-message err))))) + +(defun w/callout-holiday () + "Call to respond to the current holiday." + (w/friend-respond "It's currently that weird period between Christmas and New Years! Say something about it please!")) + +;; (defun fig//callout-hexamedia () +;; "Call to respond to a random recent chatter's Hexamedia card collection." +;; (let* ((users (-filter #'cdr (--map (cons (car it) (fig//load-db-entry (car it) :hexamedia-cards)) (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users))) +;; (cards (cdr user)) +;; (coll (and cards (nth (random (length cards)) cards)))) +;; (when coll +;; (fig//friend-respond +;; (format +;; "%s has collected %s out of 20 cards in the %s collection. Please mention the collection name and the person collecting." +;; (car user) +;; (cdr coll) +;; (car coll)))))) + +;; (defun fig//callout-copfish () +;; "Call to respond to a random recent chatter's Copfish ratio." +;; (let* ((users (-filter #'cdr (--map (cons (car it) (fig//load-db-entry (car it) :copfish-ratio)) (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users)))) +;; (when user +;; (fig//friend-respond +;; (format +;; "%s has collected %s out of %s fish in the Copfish fish catching collection. Please mention the collection name and the person collecting." +;; (car user) +;; (cadr user) +;; (cddr user)))))) + +;; (defun fig//callout-uwoomfie () +;; "Call to respond to a random recent chatter's Uwoomfie status." +;; (let* ((users +;; (-filter +;; #'cdr +;; (--map +;; (cons (car it) (fig//get-uwoomfie-status (car it))) +;; (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users)))) +;; (cl-case (cdr user) +;; (cool (fig//friend-respond (format "According to uwu_to_owo, %s is a very cool person. Make sure to mention their username." (car user)))) +;; (honored (fig//friend-respond (format "According to uwu_to_owo, %s is an honorary viewer. Make sure to mention their username." (car user)))) +;; (t nil)))) + +;; (defun fig//callout-shindaggers () +;; "Call to respond to a random recent chatter's Shindaggers knife collection." +;; (let* ((users (-filter #'cdr (--map (cons (car it) (fig//load-db-entry (car it) :shindaggers-knives)) (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users))) +;; (knives (cdr user)) +;; (knife (and knives (nth (random (length knives)) knives)))) +;; (when knife +;; (fig//friend-respond +;; (format +;; "%s has collected the %s from shindig's Shindaggers knife collection. Please mention the collection name and the person collecting and the knife." +;; (car user) +;; knife))))) + +;; (defun fig//callout-aoc () +;; "Call to respond to a random recent chatter's Advent of Code completion." +;; (let* ((users (-filter #'cdr (--map (cons (car it) (fig//lookup-aoc-stars (car it))) (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users)))) +;; (fig//friend-respond +;; (format +;; "%s has been doing Advent of Code this year, and they've completed %d out of %d problems so far." +;; (car user) +;; (cdr user) +;; (fig//max-aoc-stars))))) + +;; (defun fig//callout-gcp () +;; "Call to respond to the current GCP dot." +;; (fig//gcp-dot +;; (lambda (d) +;; (fig//friend-respond +;; (format +;; "The Global Consciousness Project indicator is currently as follows: %s" +;; (fig//gcp-describe d)))))) + +;; (defun fig//callout-resolution () +;; "Call to respond to a random recent chatter's resolve." +;; (let* ((users (-filter #'cdr (--map (cons (car it) (fig//load-db-entry (car it) :resolution)) (-take 10 fig//incoming-chat-history)))) +;; (user (and users (nth (random (length users)) users)))) +;; (if (s-match (rx (one-or-more digit) (zero-or-more space) "x" (zero-or-more space) (one-or-more digit)) (cdr user)) +;; (fig//friend-respond +;; (format +;; "%s snarkily said that their New Year's resolution was a screen resolution. What do you think about this?" (car user))) +;; (fig//friend-respond +;; (format +;; "%s made a New Year's resolution to %s. Ask them how it's going!" +;; (car user) +;; (cdr user)))))) + +;; (defun fig//callout-dew () +;; "Call to respond to The Dew Situation." +;; (fig//friend-respond +;; "Someone just gave you a delicious bottle of Mountain Dew and you really like it a lot.")) + +(defun w/get-friend-offset () + "Return the number of newlines to print before \"friend\"." + (if (-contains? '(jumping) w/friend-state) + w/friend-animation + 1)) + +(defun w/get-friend-face () + "Return the eyes and mouth for \"friend\" as a list of strings." + (cl-case w/friend-state + (jumping (list "^" "^" "ww")) + + (eating (list "v" "v" "<>")) + (eating0 (list "v" "v" "<>")) + (eating1 (list "-" "-" "mw")) + (eating2 (list "-" "-" "wm")) + + (chatting (list ">" ">" "oo")) + (chatting0 (list ">" ">" "~~")) + + (t (list "-" "-" "ww")))) + +(defun w/get-friend-bubble () + "Return the text bubble for \"friend\"." + (if (> w/friend-speech-timer 0) + w/friend-speech + nil)) + +(defun w/friend-random-event () + "Activate a random \"friend\" event." + (cl-case (random 10) + ;; (0 (fig//callout-flycheck-error)) + ;; (1 (fig//callout-gcp)) + ;; (2 (fig//callout-hexamedia)) + ;; (3 (fig//callout-uwoomfie)) + ;; (4 (fig//callout-shindaggers)) + ;; (5 (fig//callout-copfish)) + ;; (6 (fig//callout-resolution)) + ;; (29 (fig/ldq)) + (t (w/friend-set-state 'jumping)))) + +(defun w/update-friend () + "Update \"friend\"'s state per tick." + (setf w/friend-animation (% (+ w/friend-animation 1) 2)) + (if (> w/friend-state-timer 0) + (cl-decf w/friend-state-timer) + (setf w/friend-state 'default)) + (if (> w/friend-speech-timer 0) + (cl-decf w/friend-speech-timer)) + (when (= (random 120) 0) + (w/friend-random-event)) + (cl-case w/friend-state + (eating (setf w/friend-state 'eating0)) + (eating0 (setf w/friend-state 'eating1)) + (eating1 (setf w/friend-state 'eating2)) + (eating2 (setf w/friend-state 'eating1)) + + (chatting (setf w/friend-state 'chatting0)) + (chatting0 (setf w/friend-state 'chatting)) + )) + +(defun w/render-friend () + "Render the \"friend\" buffer." + (save-excursion + (with-current-buffer (w/get-friend-buffer) + (setq-local cursor-type nil) + (let* + ((inhibit-read-only t) + (face (w/get-friend-face)) + (bubble (w/get-friend-bubble))) + (erase-buffer) + (w/write + (format-spec + "%a\ + /----\\ + / %l %r \\ + \\ %m / + +----+\ +" +;; "%a\ +;; ---- +;; / \\ +;; ---------- +;; / %l %r \\ +;; \\ %m / +;; +----+\ +;; " +;; "%a\ +;; oooooo +;; oooooooo +;; oo/----\\oo +;; o/ %l %r \\o +;; \\ %m / +;; +----+\ +;; " +;; "%a\ +;; /\\ +;; /\\/\\ +;; / \\ +;; / \\ +;; ~~~~~~~~~~ +;; ~~~~~~~~~~ +;; / %l %r \\ +;; \\ %m / +;; +----+\ +;; " +;; "%a\ +;; /\\ +;; / *\\ +;; / * \\ +;; / * * \\ +;; ---------- +;; / %l %r \\ +;; \\ %m / +;; +----+\ +;; " +;; "%a\ +;; --- +;; / \\ +;; / [=] \\ +;; ----------- +;; / %l %r \\ +;; \\ %m / +;; +----+\ +;; " + `((?a . ,(s-repeat (w/get-friend-offset) " \n")) + (?l . ,(car face)) + (?r . ,(cadr face)) + (?m . ,(caddr face))))) + (goto-char (point-min)) + (end-of-line) + (w/write (or bubble "")) + (forward-line) + (end-of-line) + (w/write (if bubble "/" "")) + )))) + +(defvar w/friend-timer nil) +(defun w/run-friend-timer () + "Run the \"friend\" timer." + (when w/friend-timer + (cancel-timer w/friend-timer)) + (w/update-friend) + (w/render-friend) + (setq + w/friend-timer + (run-with-timer 1 nil #'w/run-friend-timer))) + +(defun w/start-friend () + "Launch \"friend\"." + (interactive) + (w/run-friend-timer)) + +(defun w/stop-friend () + "Stop \"friend\"." + (interactive) + (cancel-timer w/friend-timer) + (message "\"friend\" is going to sleep!")) + +(provide 'wasp-friend) +;;; wasp-friend.el ends here |
