summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSacha Chua <sacha@sachachua.com>2022-10-19 18:39:39 -0400
committerSacha Chua <sacha@sachachua.com>2022-10-19 18:39:39 -0400
commit0713dab84f270bdc1da1f0c5134962377b663319 (patch)
tree787d2127f0eec5a85ccc18301c8ce21b86135a37
parentadbd52f8f58b65416d2a12df4682eb79aaf9b9c7 (diff)
downloademacsconf-el-0713dab84f270bdc1da1f0c5134962377b663319.tar.xz
emacsconf-el-0713dab84f270bdc1da1f0c5134962377b663319.zip
ical, BBB, and pad
-rw-r--r--emacsconf-ical.el42
-rw-r--r--emacsconf-pad.el53
-rw-r--r--emacsconf-publish.el137
-rw-r--r--emacsconf.el348
4 files changed, 326 insertions, 254 deletions
diff --git a/emacsconf-ical.el b/emacsconf-ical.el
index c0cf4e4..e6e5e68 100644
--- a/emacsconf-ical.el
+++ b/emacsconf-ical.el
@@ -24,6 +24,9 @@
;;; Code:
+(defvar emacsconf-ical-public-directory (concat "/ssh:media:/var/www/media.emacsconf.org/" emacsconf-year)
+ "Directory to post ics files to.")
+
(defun emacsconf-ical-send-via-email ()
(interactive)
(let ((ical-file (expand-file-name
@@ -31,7 +34,7 @@
(expand-file-name "ics" (file-name-directory emacsconf-org-file))))
(ical-entry (emacsconf-ical-convert-entry-to-string (format-time-string "%Y%m%dT%H%M%SZ" nil t))))
(with-temp-file ical-file
- (insert ical-entry))
+ (insert ical-entry))
(emacsconf-mail-speaker "Calendar entry")
(mml-attach-file ical-file)))
@@ -69,18 +72,14 @@
(concat "DTSTART:" (format-time-string "%Y%m%dT%H%M%SZ" (plist-get o :start-time) t))
(concat "DTEND:" (format-time-string "%Y%m%dT%H%M%SZ" (plist-get o :end-time) t))
(if updated (concat "DTSTAMP:" updated))
- (if (plist-get o :speakers)
- (mapconcat
- (lambda (s)
- (format "ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;CN=\"%s\":invalid:nomail" s))
- (split-string (plist-get o :speakers) ", +")
- "\r\n")
- nil)
(string-trim
(org-icalendar-fold-string
(org-icalendar-cleanup-string
(concat "DESCRIPTION: Times are approximate and will probably change.\n"
"https://emacsconf.org/" emacsconf-year "/talks/" (plist-get o :slug) "\n"
+ (emacsconf-surround (if (length= (plist-get o :speakers) 1) "Speaker: " "Speakers: ")
+ (plist-get o :speakers)
+ "\n" "")
(plist-get o :markdown)))))
"END:VEVENT"))
"\r\n"))
@@ -104,7 +103,26 @@
"END:VCALENDAR")
"\r\n")))
-(defun emacsconf-generate-ical ()
- (unless (file-directory-p emacsconf-directory) (error "Please specify the wiki directory in the emacsconf-directory variable."))
- (with-temp-file (expand-file-name "emacsconf.ics" (expand-file-name emacsconf-year emacsconf-directory))
- (insert (emacsconf-format-as-ical (emacsconf-get-talk-info)))))
+(defun emacsconf-ical-generate-all ()
+ (interactive)
+ (let* ((emacsconf-talk-info-functions (append emacsconf-talk-info-functions (list 'emacsconf-get-abstract-from-wiki)))
+ (info (emacsconf-get-talk-info)))
+ (emacsconf-ical-generate-main info)
+ (emacsconf-ical-generate-tracks info)))
+
+(defun emacsconf-ical-generate-main (&optional info)
+ (with-temp-file (expand-file-name "emacsconf.ics"
+ (or emacsconf-ical-public-directory
+ (expand-file-name emacsconf-year emacsconf-directory)))
+ (insert (emacsconf-format-as-ical (or info (emacsconf-get-talk-info))))))
+
+(defun emacsconf-ical-generate-tracks (&optional info)
+ (interactive)
+ (mapc (lambda (entry)
+ (let ((track (emacsconf-get-track (car entry))))
+ (when track
+ (with-temp-file (expand-file-name (format "emacsconf-%s.ics" (plist-get track :id))
+ (or emacsconf-ical-public-directory
+ (expand-file-name emacsconf-year emacsconf-directory)))
+ (insert (emacsconf-format-as-ical (cdr entry)))))))
+ (seq-group-by (lambda (o) (plist-get o :track)) (or info (emacsconf-get-talk-info)))))
diff --git a/emacsconf-pad.el b/emacsconf-pad.el
index dd00802..9137d98 100644
--- a/emacsconf-pad.el
+++ b/emacsconf-pad.el
@@ -119,6 +119,14 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
;; (emacsconf-pad-get-html "emacsconf-2022-journalism")
;; (emacsconf-pad-set-html "emacsconf-2022-journalism" "<div><strong>Hello</strong> world</div>")
+(defun emacsconf-pad-get-last-edited (pad-id)
+ (interactive "MPad ID: ")
+ (emacsconf-pad-json-request (format "%sapi/1/getLastEdited?apikey=%s&padID=%s"
+ emacsconf-pad-base
+ (url-hexify-string emacsconf-pad-api-key)
+ (url-hexify-string pad-id))
+ (called-interactively-p 'any)))
+
(defun emacsconf-pad-id (o)
(concat emacsconf-pad-slug-base "-" (plist-get o :slug)))
@@ -137,12 +145,10 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
:channel (concat "emacsconf-" (plist-get (emacsconf-get-track (plist-get o :track)) :id))
:bbb-info
(cond
- ((plist-get o :bbb-room)
- (concat "<div>Q&amp;A room: ${bbb-room}</div>"))
((null (plist-get o :q-and-a))
"<div>Q&amp;A: none</div>")
((string-match "live" (plist-get o :q-and-a))
- "<div>Q&amp;A room: to be announced</div>")
+ (format "<div>Q&amp;A room: %s</div>" (plist-get o :bbb-redirect)))
(t "<div>Q&amp;A: IRC</div>"))
:next-talk-list
(if (plist-get o :next-talks)
@@ -179,8 +185,8 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
<div>All talks: ${talks}</div>
<div><strong>${title}</strong></div>
<div>${base-url}${url} - ${speakers} - Track: ${track}</div>
-${bbb-info}
<div>Watch/participate: ${watch}</div>
+${bbb-info}
<div>IRC: ${irc-nick-details} https://chat.emacsconf.org/#/connect?join=emacsconf,emacsconf-${track-id} or #emacsconf-${track-id} on libera.chat network</div>
<div>Guidelines for conduct: ${base-url}conduct</div>
<div>See end of file for license (CC Attribution-ShareAlike 4.0 + GPLv3 or later)</div>
@@ -229,18 +235,30 @@ ${next-talk-list}
<div>Except where otherwise noted, the material on the EmacsConf pad are dual-licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International Public License; and the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) an later version. Copies of these two licenses are included in the EmacsConf wiki repository, in the COPYING.GPL and COPYING.CC-BY-SA files (https://emacsconf.org/COPYING/)</div>
<div>By contributing to this pad, you agree to make your contributions available under the above licenses. You are also promising that you are the author of your changes, or that you copied them from a work in the public domain or a work released under a free license that is compatible with the above two licenses. DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION.</div></div>"))))
+
+(defvar emacsconf-pad-force-all nil "Set to t to clear everything.")
(defun emacsconf-pad-prepopulate-talk-pad (o)
(interactive (list (let ((info (emacsconf-include-next-talks (emacsconf-get-talk-info) emacsconf-pad-number-of-next-talks)))
(emacsconf-complete-talk-info info))))
(let ((pad-id (emacsconf-pad-id o)))
(emacsconf-pad-create-pad pad-id)
- (emacsconf-pad-set-html
- pad-id
- (emacsconf-pad-initial-content o))))
+ (when (or emacsconf-pad-force-all
+ (not (emacsconf-pad-modified-p pad-id))
+ (progn
+ (browse-url (emacsconf-pad-url o))
+ (y-or-n-p (format "%s might have been modified. Reset? " (plist-get o :slug)))))
+ (emacsconf-pad-set-html
+ pad-id
+ (emacsconf-pad-initial-content o))
+ (save-window-excursion
+ (emacsconf-with-talk-heading (plist-get o :slug)
+ (let-alist (emacsconf-pad-get-last-edited pad-id)
+ (org-entry-put (point) "PAD_RESET" (number-to-string .data.lastEdited))))))))
(defun emacsconf-pad-prepopulate-all-talks (&optional info)
(interactive)
- (mapc #'emacsconf-pad-prepopulate-talk-pad (emacsconf-include-next-talks (or info (emacsconf-get-talk-info)) emacsconf-pad-number-of-next-talks)))
+ (mapc #'emacsconf-pad-prepopulate-talk-pad
+ (emacsconf-include-next-talks (or info (emacsconf-get-talk-info)) emacsconf-pad-number-of-next-talks)))
(defun emacsconf-pad-export-initial-content (o file)
(interactive
@@ -259,5 +277,24 @@ ${next-talk-list}
(emacsconf-pad-export-initial-content o dir))
(emacsconf-active-talks (emacsconf-filter-talks info))))
+(defmacro emacsconf-pad-with-heading (pad-id &rest body)
+ (declare (indent 1) (debug t))
+ `(progn
+ (with-current-buffer (find-file emacsconf-org-file)
+ (goto-char (org-find-property "CUSTOM_ID"
+ (if (string-match "^[0-9]+-\\(.*\\)$" ,pad-id)
+ (match-string 1 ,pad-id)
+ "meta")))
+ ,@body)))
+
+(defun emacsconf-pad-modified-p (pad-id)
+ (save-window-excursion
+ (save-excursion
+ (let ((cached-last-modified (emacsconf-pad-with-heading pad-id (org-entry-get (point) "PAD_RESET")))
+ (result (emacsconf-pad-get-last-edited pad-id)))
+ (let-alist result
+ (not (string= cached-last-modified
+ (number-to-string .data.lastEdited))))))))
+
(provide 'emacsconf-pad)
;;; emacsconf-pad.el ends here
diff --git a/emacsconf-publish.el b/emacsconf-publish.el
index e141e9b..8d85b76 100644
--- a/emacsconf-publish.el
+++ b/emacsconf-publish.el
@@ -54,7 +54,7 @@
"Add the current talk to the wiki."
(interactive)
(emacsconf-update-talk)
- (emacsconf-generate-info-pages)
+ (emacsconf-publish-info-pages)
(emacsconf-generate-main-schedule)
(magit-status-setup-buffer emacsconf-directory))
@@ -82,7 +82,7 @@
(when
(let ((info (emacsconf-get-talk-info))
(force (or force (yes-or-no-p "Overwrite existing talk pages? "))))
- (emacsconf-generate-info-pages info)
+ (emacsconf-publish-info-pages info)
(emacsconf-generate-main-schedule info)
(emacsconf-generate-talk-pages info force)
(magit-status emacsconf-directory))))
@@ -90,9 +90,9 @@
(defun emacsconf-update-schedules-in-wiki ()
(interactive)
(emacsconf-publish-with-wiki-change
- (emacsconf-generate-info-pages)
+ (emacsconf-publish-info-pages)
(emacsconf-generate-main-schedule)
- (emacsconf-generate-ical)
+ (emacsconf-ical-generate-all)
(emacsconf-publish-watch-pages)
(when (functionp 'emacsconf-pentabarf-generate)
(emacsconf-pentabarf-generate))))
@@ -322,41 +322,65 @@ resources."
(list "--answers.vtt" "--answers--chapters.vtt" "--answers--compressed32.webm")))
"")))
+(defun emacsconf-publish-webchat-link (o)
+ (let ((track (emacsconf-get-track (plist-get o :track))))
+ (format "<a href=\"%s?join=emacsconf,emacsconf-%s\">#emacsconf-%s</a>"
+ emacsconf-chat-base
+ (plist-get track :id)
+ (plist-get track :id))))
+(defvar emacsconf-publish-include-pads nil "When non-nil, include Etherpad info.")
(defun emacsconf-format-talk-schedule-info (o)
(let ((friendly (concat "/" emacsconf-year "/talks/" (plist-get o :slug) ))
(timestamp (org-timestamp-from-string (plist-get o :scheduled))))
- (concat
- "[[!toc ]]\n"
- "Duration: " (or (plist-get o :video-duration)
- (concat (plist-get o :duration) " minutes"))
- " \n"
- (if (plist-get o :q-and-a) (format "Q&A: %s \n" (plist-get o :q-and-a)) "")
- (if (member emacsconf-publishing-phase '(program schedule)) (concat "Status: " (plist-get o :status-label) " \n") "")
- (if (and (member emacsconf-publishing-phase '(program schedule))
- (not (member (plist-get o :status) '("DONE" "CANCELLED" "STARTED"))))
- (let ((start (org-timestamp-to-time (org-timestamp-split-range timestamp)))
- (end (org-timestamp-to-time (org-timestamp-split-range timestamp t))))
- (format
- "<div>Times in different timezones:</div><div class=\"times\" start=\"%s\" end=\"%s\"><div class=\"conf-time\">%s</div><div class=\"others\">%s</div></div><div><a href=\"/%s/watch/%s/\">Find out how to watch and participate</a></div>"
- (format-time-string "%Y-%m-%dT%H:%M:%SZ" start t)
- (format-time-string "%Y-%m-%dT%H:%M:%SZ" end t)
- (emacsconf-timezone-string o emacsconf-timezone)
- (string-join (emacsconf-timezone-strings
- o
- (seq-filter (lambda (zone) (string= emacsconf-timezone zone))
- emacsconf-timezones)) "<br />")
- emacsconf-year
- (plist-get (emacsconf-get-track (plist-get o :track)) :id)))
- "")
- "\n"
- (if (plist-get o :alternate-apac)
- (format "[[!inline pages=\"internal(%s/inline-alternate)\" raw=\"yes\"]] \n" emacsconf-year)
- "")
- "\n"
- (if (plist-get o :public) (emacsconf-wiki-talk-resources o) "")
- "\n# Description\n\n")))
+ (emacsconf-replace-plist-in-string
+ (append o
+ (list :format
+ (concat (or (plist-get o :video-duration)
+ (concat (plist-get o :duration) "-min talk"))
+ (if (plist-get o :q-and-a)
+ (format " followed by %s Q&A (%s) "
+ (plist-get o :q-and-a)
+ (if (string-match "live" (plist-get o :q-and-a))
+ (if (eq 'after (emacsconf-bbb-status o))
+ "done"
+ (format "<https://emacsconf.org/current/%s/room>" (plist-get o :slug)))
+ (emacsconf-publish-webchat-link o)))
+ ""))
+ :pad-info
+ (if emacsconf-publish-include-pads
+ (format "Pad: <https://pad.emacsconf.org/%s-%s> \n" emacsconf-year (plist-get o :slug))
+ "")
+ :status-info
+ (if (member emacsconf-publishing-phase '(program schedule)) (format "Status: %s \n" (plist-get o :status-label)) "")
+ :schedule-info
+ (if (and (member emacsconf-publishing-phase '(program schedule))
+ (not (emacsconf-talk-all-done-p o)))
+ (let ((start (org-timestamp-to-time (org-timestamp-split-range timestamp)))
+ (end (org-timestamp-to-time (org-timestamp-split-range timestamp t))))
+ (format
+ "<div>Times in different timezones:</div><div class=\"times\" start=\"%s\" end=\"%s\"><div class=\"conf-time\">%s</div><div class=\"others\">%s</div></div><div><a href=\"/%s/watch/%s/\">Find out how to watch and participate</a></div>"
+ (format-time-string "%Y-%m-%dT%H:%M:%SZ" start t)
+ (format-time-string "%Y-%m-%dT%H:%M:%SZ" end t)
+ (emacsconf-timezone-string o emacsconf-timezone)
+ (string-join (emacsconf-timezone-strings
+ o
+ (seq-filter (lambda (zone) (string= emacsconf-timezone zone))
+ emacsconf-timezones)) "<br />")
+ emacsconf-year
+ (plist-get (emacsconf-get-track (plist-get o :track)) :id)))
+ "")))
+ (concat
+ "[[!toc ]]
+Format: ${format}
+${pad-info}${status-info}${schedule-info}\n"
+ (if (plist-get o :alternate-apac)
+ (format "[[!inline pages=\"internal(%s/inline-alternate)\" raw=\"yes\"]] \n" emacsconf-year)
+ "")
+ "\n"
+ (if (plist-get o :public) (emacsconf-wiki-talk-resources o) "")
+ "\n# Description\n\n"))))
(defun emacsconf-format-email-questions-and-comments (talk)
(format "Questions or comments? Please e-mail %s"
@@ -463,7 +487,7 @@ Back to the [[talks]] \n"
"</div>
")))))))
-(defun emacsconf-generate-info-pages (&optional info)
+(defun emacsconf-publish-info-pages (&optional info)
"Populate year/info/*-nav, -before, and -after files."
(interactive)
(setq info (or info (emacsconf-get-talk-info)))
@@ -1304,4 +1328,47 @@ ${talks}
"))))
emacsconf-tracks))))
+(defvar emacsconf-publish-current-dir "/ssh:media:/var/www/media.emacsconf.org/2022/current"
+ "Directory to publish BBB redirects and current information to.")
+
+
+;; (assert (eq (emacsconf-get-bbb-state '(:status "OPEN_Q")) 'open))
+;; (assert (eq (emacsconf-get-bbb-state '(:status "TO_ARCHIVE")) 'after))
+
+(defun emacsconf-publish-bbb-redirect (talk)
+ (interactive (list (emacsconf-complete-talk-info)))
+ (let ((bbb-filename (expand-file-name (format "bbb-%s.html" (plist-get talk :slug))
+ emacsconf-publish-current-dir))
+ (bbb-redirect-url (concat "https://media.emacsconf.org/" emacsconf-year "/current/bbb-" (plist-get talk :slug) ".html"))
+ (status (emacsconf-bbb-status talk)))
+ (with-temp-file bbb-filename
+ (insert
+ (emacsconf-replace-plist-in-string
+ (append talk (list :base-url emacsconf-base-url :bbb-redirect-url bbb-redirect-url))
+ (pcase status
+ ('open
+ "<html><head><meta http-equiv=\"refresh\" content=\"0; URL=${bbb-room}\"></head><body>
+The live Q&A room for ${title} is now open. You should be redirected to <a href=\"${bbb-room}\">${bbb-room}</a> automatically, but if not, please visit the URL manually to join the Q&A.</body></html>")
+ ('before
+ "<html><head><meta http-equiv=\"refresh\" content=\"5; URL=${bbb-redirect-url}\"></head><body>
+The Q&A room for ${title} is not yet open. This page will refresh every 5 seconds until the BBB room is marked as open, or you can refresh it manually.</body></html>")
+ ('after
+ "<html><head><body>
+The Q&A room for ${title} has finished. You can find more information about the talk at <a href=\"${base-url}${url}\">${base-url}${url}</a>.</body></html>")
+ (_
+ "<html><head><body>
+There is no live Q&A room for ${title}. You can find more information about the talk at <a href=\"${base-url}${url}\">${base-url}${url}</a>.</body></html>"
+ )
+ ))))))
+(defun emacsconf-publish-bbb-redirect-all ()
+ (interactive)
+ (unless (file-directory-p emacsconf-publish-current-dir)
+ (make-directory emacsconf-publish-current-dir))
+ (mapc #'emacsconf-publish-bbb-redirect (emacsconf-filter-talks (emacsconf-get-talk-info))))
+;; (emacsconf-publish-bbb-redirect '(:slug "test" :status "TO_STREAM" :bbb-room "https://bbb.emacsverse.org/b/sac-fwh-pnz-ogz" :title "Test room" :q-and-a "live" :url "2022/talks/test"))
+;; (emacsconf-publish-bbb-redirect '(:slug "test" :status "CLOSED_Q" :bbb-room "https://bbb.emacsverse.org/b/sac-fwh-pnz-ogz" :title "Test room" :q-and-a "live" :url "2022/talks/test"))
+;; (emacsconf-publish-bbb-redirect '(:slug "test" :status "OPEN_Q" :bbb-room "https://bbb.emacsverse.org/b/sac-fwh-pnz-ogz" :title "Test room" :q-and-a "live" :url "2022/talks/test"))
+;; (emacsconf-publish-bbb-redirect '(:slug "test" :status "UNSTREAMED_Q" :bbb-room "https://bbb.emacsverse.org/b/sac-fwh-pnz-ogz" :title "Test room" :q-and-a "live" :url "2022/talks/test"))
+;; (emacsconf-publish-bbb-redirect '(:slug "test" :status "TO_ARCHIVE" :bbb-room "https://bbb.emacsverse.org/b/sac-fwh-pnz-ogz" :title "Test room" :q-and-a "live" :url "2022/talks/test"))
+
(provide 'emacsconf-publish)
diff --git a/emacsconf.el b/emacsconf.el
index d594f15..112281a 100644
--- a/emacsconf.el
+++ b/emacsconf.el
@@ -195,7 +195,20 @@
,@body))
(defvar emacsconf-status-types
- '(("WAITING_FOR_PREREC" . "Waiting for video from speaker")))
+ '(("WAITING_FOR_PREREC" . "Waiting for video from speaker")
+ ("TO_PROCESS" . "Processing uploaded video")
+ ("TO_AUTOCAP" . "Processing uploaded video")
+ ("TO_ASSIGN" . "Waiting for a caption volunteer")
+ ("TO_CAPTION" . "Processing uploaded video")
+ ("TO_STREAM" . "Talk captioned")
+ ("PLAYING" . "Now playing on the conference livestream")
+ ("CLOSED_Q" . "Q&A starting (not yet open for joining)")
+ ("OPEN_Q" . "Q&A open for participation")
+ ("UNSTREAMED_Q" . "Q&A continues off the stream")
+ ("TO_ARCHIVE" . "Q&A finished, IRC and pad will be archived on this page")
+ ("TO_EXTRACT" . "Q&A to be extracted from the room recordings")
+ ("DONE" . "All done")
+ ("CANCELLED" . "Talk cancelled")))
(defun emacsconf-get-talk-categories (o)
(org-narrow-to-subtree)
@@ -211,8 +224,7 @@
(:track "TRACK")
(:slug "SLUG")
(:video-slug "VIDEO_SLUG")
- (:public "PUBLIC")
- (:qa-public "QA_PUBLIC")
+ (:qa-public "QA_PUBLIC") ; now tracked by the OPEN_Q and UNSTREAMED_Q status
(:scheduled "SCHEDULED")
(:uuid "UUID")
(:email "EMAIL")
@@ -223,6 +235,7 @@
(:bbb-room "ROOM")
(:irc "IRC")
(:intro-note "INTRO_NOTE")
+ (:public "PUBLIC")
(:check-in "CHECK_IN")
(:contact "CONTACT")
(:captioner "CAPTIONER")
@@ -232,6 +245,7 @@
(:qa-toobnix "QA_TOOBNIX")
(:pronunciation "PRONUNCIATION")
(:pronouns "PRONOUNS")
+ (:prerec-info "PREREC_INFO")
(:public-email "PUBLIC_EMAIL")
(:buffer "BUFFER")
(:duration "TIME")
@@ -263,25 +277,27 @@
(concat (org-entry-get (point) "SLUG") ".md")
(expand-file-name "captions" (expand-file-name emacsconf-year emacsconf-directory)))
:conf-year emacsconf-year
+ :bbb-redirect (format "https://emacsconf.org/current/%s/room/" (org-entry-get (point) "SLUG"))
+ :pad-url (format "https://pad.emacsconf.org/%s-%s" emacsconf-year (org-entry-get (point) "SLUG"))
:start-time (when (org-entry-get (point) "SCHEDULED")
- (date-to-time
- (concat
- (format-time-string "%Y-%m-%dT%H:%M:%S"
- (org-timestamp-to-time
- (org-timestamp-split-range
- (org-timestamp-from-string
- (org-entry-get (point) "SCHEDULED")))))
- emacsconf-timezone-offset)))
+ (date-to-time
+ (concat
+ (format-time-string "%Y-%m-%dT%H:%M:%S"
+ (org-timestamp-to-time
+ (org-timestamp-split-range
+ (org-timestamp-from-string
+ (org-entry-get (point) "SCHEDULED")))))
+ emacsconf-timezone-offset)))
:end-time (when (org-entry-get (point) "SCHEDULED")
- (date-to-time
- (concat
- (format-time-string "%Y-%m-%dT%H:%M:%S"
- (org-timestamp-to-time
- (org-timestamp-split-range
- (org-timestamp-from-string
- (org-entry-get (point) "SCHEDULED"))
- t)))
- emacsconf-timezone-offset))))
+ (date-to-time
+ (concat
+ (format-time-string "%Y-%m-%dT%H:%M:%S"
+ (org-timestamp-to-time
+ (org-timestamp-split-range
+ (org-timestamp-from-string
+ (org-entry-get (point) "SCHEDULED"))
+ t)))
+ emacsconf-timezone-offset))))
(let* ((entry-props (org-entry-properties)))
(mapcar
(lambda (o) (list (car o) (assoc-default (cadr o) entry-props)))
@@ -354,8 +370,29 @@
(defun emacsconf-add-talk-status (o)
(plist-put o :status-label
- (assoc-default (plist-get o :status)
- emacsconf-status-types 'string= "")))
+ (or (assoc-default (plist-get o :status)
+ emacsconf-status-types 'string= "")
+ (plist-get o :status)))
+ (if (member (plist-get o :status)
+ (split-string "PLAYING CLOSED_Q OPEN_Q UNSTREAMED_Q TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP"))
+ (plist-put o :public t))
+ o)
+
+(defun emacsconf-test-public-states ()
+ (let ((states (split-string (replace-regexp-in-string "(.*?)" "" "TODO(t) TO_REVIEW TO_ACCEPT TO_CONFIRM WAITING_FOR_PREREC(w) TO_PROCESS(p) TO_AUTOCAP(y) TO_ASSIGN(a) TO_CAPTION(c) TO_STREAM(s) PLAYING(m) CLOSED_Q(q) OPEN_Q(o) UNSTREAMED_Q(u) TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP"))))
+ (mapc (lambda (state) (assert (null (plist-get (emacsconf-add-talk-status
+ (list :status state))
+ :public))))
+ (subseq states
+ 0
+ (seq-position
+ states "PLAYING")))
+ (mapc (lambda (state) (assert (plist-get (emacsconf-add-talk-status (list
+ :status
+ state))
+ :public)))
+ (subseq states (seq-position
+ states "PLAYING")))))
(defvar emacsconf-talk-info-functions
'(emacsconf-get-talk-info-from-properties
@@ -556,182 +593,6 @@
("RET" emacsconf-go-to-talk))
(add-to-list 'embark-keymap-alist '(emacsconf . embark-emacsconf-actions)))
-;;; Mail merge
-
-(defun emacsconf-mail-complete-email-group (&optional info)
- "Return (email . (talk talk))."
- (setq info (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))
- (save-window-excursion
- (let* ((grouped (seq-group-by (lambda (o) (plist-get o :email)) info))
- (slug (emacsconf-get-slug-from-string (emacsconf-complete-talk)))
- (email (plist-get (seq-find (lambda (o) (string= (plist-get o :slug) slug)) info) :email)))
- (assoc email grouped))))
-
-(defun emacsconf-mail-prepare (template group attrs)
- (compose-mail
- (car group)
- (emacsconf-replace-plist-in-string attrs (plist-get template :subject))
- `(("Reply-To" . ,(emacsconf-replace-plist-in-string attrs (plist-get template :reply-to)))
- ("Mail-Followup-To" . ,(emacsconf-replace-plist-in-string attrs (plist-get template :mail-followup-to)))
- ("Cc" . ,(plist-get template :cc))))
- (message-sort-headers)
- (message-goto-body)
- (save-excursion (insert (emacsconf-replace-plist-in-string attrs (plist-get template :body)))
- (goto-char (point-min))
- (emacsconf-mail-merge-wrap)))
-
-(defun emacsconf-mail-template-to-me ()
- "Might be useful for testing."
- (interactive)
- (let* ((template (if (org-entry-get (point) "EMAIL_ID")
- (emacsconf-mail-merge-get-template-from-subtree)
- (emacsconf-mail-merge-get-template
- (completing-read "Template: " (org-property-values "EMAIL_ID")))))
- (mail-func (plist-get template :function))
- (group (emacsconf-mail-complete-email-group)))
- (funcall mail-func (cons user-mail-address (cdr group)) template)))
-
-(defun emacsconf-mail-template-to-group ()
- "Prompt for a speaker and e-mail current template to them."
- (interactive)
- (let* ((template (if (org-entry-get (point) "EMAIL_ID")
- (emacsconf-mail-merge-get-template-from-subtree)
- (emacsconf-mail-merge-get-template
- (completing-read "Template: " (org-property-values "EMAIL_ID")))))
- (mail-func (plist-get template :function)))
- (funcall mail-func (emacsconf-mail-complete-email-group) template)))
-
-(defun emacsconf-mail-template-to-all ()
- "Uses the current template to draft messages to all the speakers."
- (interactive)
- (let* ((template (if (org-entry-get (point) "EMAIL_ID")
- (emacsconf-mail-merge-get-template-from-subtree)
- (emacsconf-mail-merge-get-template
- (completing-read "Template: " (org-property-values "EMAIL_ID")))))
- (info (seq-filter (lambda (o)
- (if (plist-get template :slugs)
- (member (plist-get o :slug)
- (split-string (plist-get template :slugs) " "))
- t))
- (emacsconf-filter-talks (emacsconf-get-talk-info))))
- (grouped (emacsconf-mail-group-by-email info))
- (mail-func (plist-get template :function)))
- (mapc (lambda (group)
- (funcall mail-func group template))
- grouped)))
-
-(defun emacsconf-mail-group-by-email (info)
- (seq-group-by (lambda (o) (plist-get o :email)) info))
-
-(defun emacsconf-mail-speaker (&optional subject body)
- "Compose a message to the speaker of the current talk."
- (interactive)
- (compose-mail (format "%s <%s>" (org-entry-get (point) "NAME") (org-entry-get (point) "EMAIL")) subject)
- (when body (message-goto-body) (insert body)))
-
-(defun emacsconf-mail-speaker-schedule (&optional subject body)
- (interactive (list (read-string "Subject: ") nil))
- (let ((info (emacsconf-get-talk-info-for-subtree)))
- (emacsconf-mail-speaker subject body)
- (when body (message-goto-body) (insert body))
- (goto-char (point-max))
- (insert (string-join (emacsconf-timezone-strings info) "\n"))))
-
-(defvar emacsconf-submit-email "emacsconf-submit@gnu.org" "E-mail address for submissions.")
-
-(defun emacsconf-mail-speaker-cc-submit (&optional subject body)
- "Compose a message to the speaker of the current talk."
- (interactive)
- (compose-mail (format "%s <%s>" (org-entry-get (point) "NAME") (org-entry-get (point) "EMAIL"))
- subject '(("Reply-To" . emacsconf-submit-email) ("Cc" . emacsconf-submit-email)))
- (message-goto-body)
- (when body (insert body))
- (save-excursion (insert "Please keep " emacsconf-submit-email " in the To: or Cc: when replying. Thank you!")))
-
-(defun emacsconf-show-talk-info-for-mail ()
- (interactive)
- (let ((email (or (mail-fetch-field "reply-to") (mail-fetch-field "from"))))
- (when (string-match "<\\(\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+\\)>" email)
- (setq email (match-string 1 email)))
- (pop-to-buffer (find-file-noselect emacsconf-org-file))
- (goto-char (point-min))
- (goto-char
- (or (org-find-property "EMAIL" email)
- (org-find-property "NAME"
- (completing-read "Name: " (delq nil (org-map-entries (lambda () (org-entry-get "NAME"))))))))))
-
-(defun emacsconf-mail-merge-wrap ()
- (interactive)
- (with-undo-amalgamate
- (save-excursion
- (while (re-search-forward " *${wrap}" nil t)
- (replace-match "")
- (fill-paragraph)))))
-
-(defun emacsconf-mail-merge-get-template-from-subtree ()
- (list :subject (org-entry-get-with-inheritance "SUBJECT")
- :cc (org-entry-get-with-inheritance "CC")
- :slugs (org-entry-get-with-inheritance "SLUGS")
- :reply-to (or (org-entry-get-with-inheritance "REPLY_TO") (org-entry-get-with-inheritance "REPLY-TO"))
- :mail-followup-to (or (org-entry-get-with-inheritance "MAIL_FOLLOWUP_TO")
- (org-entry-get-with-inheritance "MAIL-FOLLOWUP-TO"))
- :body (replace-regexp-in-string "\n *," "\n" (buffer-substring-no-properties
- (progn (org-end-of-meta-data) (point))
- (org-end-of-subtree)))
- :function (when (org-entry-get-with-inheritance "FUNCTION")
- (intern (org-entry-get-with-inheritance "FUNCTION")))))
-
-(defun emacsconf-mail-merge-get-template (id)
- "Return the information for the e-mail template with EMAIL_ID set to ID."
- (save-excursion
- (goto-char (org-find-property "EMAIL_ID" id))
- (emacsconf-mail-merge-get-template-from-subtree)))
-
-(defun emacsconf-mail-merge-fill (string)
- "Fill in the values for STRING using the properties at point.
-Include some other things, too, such as emacsconf-year, title, name, email, url, and duration."
- (let (start (values `(("year" . ,emacsconf-year)
- ("title" . ,(org-entry-get (point) "ITEM"))
- ("name" . ,(org-entry-get (point) "NAME"))
- ("email" . ,(org-entry-get (point) "EMAIL"))
- ("url" . ,(format "%s%s/talks/%s" emacsconf-base-url emacsconf-year (org-entry-get (point) "SLUG")))
- ("duration" . ,(org-entry-get (point) "TIME")))))
- (while (string-match "\\${\\([-a-zA-Z_]+?\\)}" string start)
- (if (assoc-default (match-string 1 string) values)
- (setq string (replace-match (assoc-default (match-string 1 string) values) t t string))
- (setq string (replace-match (save-match-data (org-entry-get (point) (match-string 1 string))) t t string)))
- (setq start (1+ (match-beginning 0))))
- string))
-
-(defun emacsconf-mail-merge-format-email-address-for-subtree ()
- (if (string-match "," (org-entry-get (point) "EMAIL"))
- (org-entry-get (point) "EMAIL")
- (format "%s <%s>" (org-entry-get (point) "NAME") (org-entry-get (point) "EMAIL"))))
-
-(defun emacsconf-mail-merge-for-subtree (id note-field)
- (let* ((template (emacsconf-mail-merge-get-template id))
- (body (emacsconf-mail-merge-fill (plist-get template :body)))
- (subject (emacsconf-mail-merge-fill (plist-get template :subject)))
- (note (org-entry-get (point) note-field)))
- (compose-mail (emacsconf-mail-merge-format-email-address-for-subtree)
- subject
- `(("Reply-To" . ,(plist-get template :reply-to))
- ("Mail-Followup-To" . ,(plist-get template :mail-followup-to))
- ("Cc" . ,(plist-get template :cc))))
- (message-goto-body)
- (save-excursion
- (when note (insert "#+NOTE: " note "\n======== Delete above before sending =============\n\n"))
- (insert body))))
-
-(defun emacsconf-cancel-mail-merge ()
- (interactive)
- (mapc (lambda (buffer)
- (when (string-match "unsent" (buffer-name buffer))
- (let ((kill-buffer-query-functions nil)
- (buffer-modified-p nil))
- (kill-buffer buffer))))
- (buffer-list)))
-
;;; Status updates
(defun emacsconf-status-update ()
@@ -917,7 +778,9 @@ Include some other things, too, such as emacsconf-year, title, name, email, url,
(:name "Development" :color "skyblue" :id "dev")))
(defun emacsconf-get-track (name)
- (seq-find (lambda (track) (string= name (plist-get track :name))) emacsconf-tracks))
+ (seq-find (lambda (track) (or (string= name (plist-get track :name))
+ (string= name (plist-get track :id))))
+ emacsconf-tracks))
(defun emacsconf-by-track (info)
(mapcar (lambda (track)
@@ -936,5 +799,92 @@ Include some other things, too, such as emacsconf-year, title, name, email, url,
info)
#'emacsconf-sort-by-scheduled)))
+(defun emacsconf-filter-talks-by-track (track info)
+ (when (stringp track) (setq track (emacsconf-get-track track)))
+ (seq-filter (lambda (o) (string= (plist-get o :track) (plist-get track :name))) info))
+
+(defvar emacsconf-shifts
+ `((:id "sat-am"
+ :label "Sat Dec 3 morning"
+ :start ,(concat "2022-12-03T09:00:00" emacsconf-timezone-offset)
+ :end ,(concat "2022-12-03T12:00:00" emacsconf-timezone-offset))
+ (:id "sat-pm"
+ :label "Sat Dec 3 afternoon"
+ :start ,(concat "2022-12-03T13:00:00" emacsconf-timezone-offset)
+ :end ,(concat "2022-12-03T17:30:00" emacsconf-timezone-offset))
+ (:id "sun-am"
+ :label "Sun Dec 4 morning"
+ :start ,(concat "2022-12-04T09:00:00" emacsconf-timezone-offset)
+ :end ,(concat "2022-12-04T12:00:00" emacsconf-timezone-offset))
+ (:id "sun-pm"
+ :label "Sun Dec 4 afternoon"
+ :start ,(concat "2022-12-04T13:00:00" emacsconf-timezone-offset)
+ :end ,(concat "2022-12-04T17:30:00" emacsconf-timezone-offset))))
+
+(defun emacsconf-filter-talks-by-time (start-time end-time info)
+ "Return talks that are between START-TIME and END-TIME (inclusive) in INFO."
+ (when (stringp start-time) (setq start-time (date-to-time start-time)))
+ (when (stringp end-time) (setq end-time (date-to-time end-time)))
+ (seq-filter (lambda (o)
+ (and (not (time-less-p (plist-get o :start-time) end-time))
+ (not (time-less-p start-time (plist-get o :end-time)))))
+ info))
+
+(defun emacsconf-get-shift (time)
+ "Return the shift that TIME is in."
+ (unless (stringp time)
+ (setq time (format-time-string "%Y-%m-%dT%H:%M:%S%z" time emacsconf-timezone)))
+ (seq-find (lambda (shift)
+ (and (not (string> time (plist-get shift :end)))
+ (not (string> (plist-get shift :start) time))))
+ emacsconf-shifts))
+
+(defun emacsconf-filter-talks-by-shift (time track info)
+ "Return a list of talks that are in the shift specified by TIME.
+Filter by TRACK if given. Use INFO as the list of talks."
+ (let* ((shift (emacsconf-get-shift time))
+ (list (emacsconf-filter-talks-by-time (plist-get shift :start) (plist-get shift :end) info)))
+ (if track
+ (emacsconf-filter-talks-by-track track info)
+ list)))
+
+(defun emacsconf-talk-all-done-p (talk)
+ (member (plist-get talk :status) (split-string "TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP DONE")))
+
+(defun emacsconf-bbb-status (talk)
+ (let ((states
+ '((open . "OPEN_Q UNSTREAMED_Q")
+ (before . "TODO TO_REVIEW TO_ACCEPT WAITING_FOR_PREREC TO_PROCESS TO_AUTOCAP TO_ASSIGN TO_CAPTION TO_STREAM PLAYING CLOSED_Q")
+ (after . "TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP DONE"))))
+ (if (string-match "live" (or (plist-get talk :q-and-a) ""))
+ (or (car (seq-find (lambda (state)
+ (member (plist-get talk :status) (split-string (cdr state))))
+ states))
+ (throw 'error "Unknown talk BBB state"))
+ 'irc)))
+
+(defvar emacsconf-bbb-base-url "https://bbb.emacsverse.org/" "Include trailing slash.")
+(defun emacsconf-bbb-room-title-list (&optional info)
+ (delq nil
+ (mapcar
+ (lambda (o)
+ (when (car o)
+ (concat "ec" (substring emacsconf-year 2)
+ "-" (plist-get (emacsconf-get-shift (plist-get (cadr o) :start-time)) :id)
+ "-" (plist-get (emacsconf-get-track (plist-get (cadr o) :track)) :id)
+ " " (car o)
+ " ("
+ (mapconcat (lambda (talk) (plist-get talk :slug)) (cdr o) ", ")
+ ")")))
+ (seq-group-by (lambda (o) (plist-get o :speakers))
+ (or info (emacsconf-active-talks (emacsconf-filter-talks (emacsconf-get-talk-info))))))))
+
+(defun emacsconf-surround (before text after alternative)
+ "Concat BEFORE, TEXT, and AFTER if TEXT is specified, or return ALTERNATIVE."
+ (if (and text (not (string= text "")))
+ (concat (or before "") text (or after ""))
+ alternative))
+;;
+
(provide 'emacsconf)
;;; emacsconf.el ends here