diff options
Diffstat (limited to 'emacs/.emacs.d')
31 files changed, 939 insertions, 89 deletions
diff --git a/emacs/.emacs.d/init/ycp-basic.el b/emacs/.emacs.d/init/ycp-basic.el index 12c21d1..6baf1b8 100644 --- a/emacs/.emacs.d/init/ycp-basic.el +++ b/emacs/.emacs.d/init/ycp-basic.el @@ -57,6 +57,7 @@ (:delay 5) (my-setq-from-local my-audio-incoming-dir my-video-incoming-dir my-document-incoming-dir) + (my-setq-from-local my-copy-file-targets) (my-keybind global-map "C-c <f2>" #'my-rename-file-and-buffer "C-c <delete>" #'my-delete-file-and-kill-buffer diff --git a/emacs/.emacs.d/init/ycp-editing.el b/emacs/.emacs.d/init/ycp-editing.el index 907c80b..d497f42 100644 --- a/emacs/.emacs.d/init/ycp-editing.el +++ b/emacs/.emacs.d/init/ycp-editing.el @@ -30,6 +30,8 @@ ;; line wrap at window edge (setq-default truncate-lines nil) (setq kill-do-not-save-duplicates t) +(setq kill-transform-function + (lambda (s) (when (string-match-p "[^ \t\n]" s) s))) (setq bidi-inhibit-bpa t) (setq save-interprogram-paste-before-kill t) (setq kill-ring-max 200) diff --git a/emacs/.emacs.d/init/ycp-emms.el b/emacs/.emacs.d/init/ycp-emms.el index a3a4604..458a6b0 100644 --- a/emacs/.emacs.d/init/ycp-emms.el +++ b/emacs/.emacs.d/init/ycp-emms.el @@ -90,6 +90,8 @@ (my-override emms-mode-line-toggle) (add-hook 'emms-playlist-selection-changed-hook 'my-emms-output-current-track-to-i3bar-file) + (add-hook 'emms-player-finished-hook 'my-emms-score-up-playing) + (add-hook 'emms-player-started-hook 'my-emms-score-up-chosen-bonus) (setq emms-player-next-function 'my-emms-next-track-or-random-album) (setq emms-players-preference-f 'my-emms-players-preference) (my-keybind dired-mode-map "e" #'my-dired-add-to-emms) diff --git a/emacs/.emacs.d/init/ycp-gnus.el b/emacs/.emacs.d/init/ycp-gnus.el index f4886fd..9e89ee9 100644 --- a/emacs/.emacs.d/init/ycp-gnus.el +++ b/emacs/.emacs.d/init/ycp-gnus.el @@ -186,6 +186,10 @@ (setq gnus-summary-next-group-on-exit nil) ) +(my-package gnus-art + (my-keybind gnus-article-mode-map + "w" #'my-copy-url-at-point)) + (my-package nnrss (:delay 60) (setq nnrss-use-local t)) diff --git a/emacs/.emacs.d/init/ycp-markup.el b/emacs/.emacs.d/init/ycp-markup.el index e03fd86..f3d92d0 100644 --- a/emacs/.emacs.d/init/ycp-markup.el +++ b/emacs/.emacs.d/init/ycp-markup.el @@ -83,17 +83,33 @@ (setq-local completion-cycle-threshold t) (setq-local ledger-complete-in-steps t) (setq-local company-mode nil))) - (setq ledger-binary-path "hledger")) + (setq ledger-binary-path "hledger") + (require 'my-ledger) + (my-keybind ledger-mode-map + "M-<down>" #'my-ledger-move-xact-down + "M-<up>" #'my-ledger-move-xact-up + "C-c C-c" #'compile) + (add-to-list 'compilation-error-regexp-alist 'ledger) + (add-to-list 'compilation-error-regexp-alist-alist my-ledger-compilation-error-re) + (add-hook 'ledger-mode-hook 'my-ledger-set-compile-command) + ) ;;; todo: open epub in emacs client with nov (my-package nov (:delay 15) (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) - (setq nov-text-width fill-column) + ;; No fill, so it requires visual line mode to look nice + (setq nov-text-width t) + (add-hook 'nov-mode-hook 'visual-line-mode) (add-hook 'nov-mode-hook 'follow-mode) + (add-hook 'nov-mode-hook (lambda () + (setq next-screen-context-lines 4))) + (add-hook 'nov-post-html-render-hook 'my-nov-set-margins) (require 'my-nov) (my-override nov-render-title) (my-override nov-scroll-up) + (my-keybind nov-mode-map + "Q" #'my-nov-copy-buffer-file-with-staging) ) ;;; json-mode diff --git a/emacs/.emacs.d/init/ycp-org.el b/emacs/.emacs.d/init/ycp-org.el index eb5a63d..6385a46 100644 --- a/emacs/.emacs.d/init/ycp-org.el +++ b/emacs/.emacs.d/init/ycp-org.el @@ -441,7 +441,11 @@ (add-to-list 'org-protocol-protocol-alist '("grok" :protocol "grok" - :function my-org-protocol-grok))) + :function my-org-protocol-grok)) + (add-to-list 'org-protocol-protocol-alist + '("browse-url" + :protocol "browse-url" + :function my-org-protocol-browse-url))) ;; org man links (my-package ol-man @@ -504,12 +508,12 @@ (my-package my-org (:delay 30) (require 'my-web) - (org-link-set-parameters "http" :follow (lambda (url arg) - (my-browse-url - (concat "http:" url) arg))) - (org-link-set-parameters "https" :follow (lambda (url arg) - (my-browse-url - (concat "https:" url) arg))) + (org-link-set-parameters "http" :follow + (lambda (url arg) + (browse-url (concat "http:" url) arg))) + (org-link-set-parameters "https" :follow + (lambda (url arg) + (browse-url (concat "http:" url) arg))) (require 'eww) (define-key eww-mode-map (kbd "C-'") 'my-eww-org-protocol-grok) ) @@ -518,5 +522,21 @@ (:delay 60) (require 'my-ox-jira)) +(my-package org-remark + (:install t) + (:delay 60) + (require 'my-org-remark) + (setq org-remark-notes-display-buffer-action + '(display-buffer-reuse-mode-window)) + (require 'nov) + (my-keybind nov-mode-map + "M-n" #'org-remark-next + "M-p" #'org-remark-prev + "M" #'my-org-remark-open-or-create + "o" #'org-remark-view + "d" #'org-remark-delete) + (with-eval-after-load 'nov + (org-remark-nov-mode +1))) + (provide 'ycp-org) ;;; ycp-org.el ends here diff --git a/emacs/.emacs.d/init/ycp-pdf.el b/emacs/.emacs.d/init/ycp-pdf.el index 9553f7a..8e47f1c 100644 --- a/emacs/.emacs.d/init/ycp-pdf.el +++ b/emacs/.emacs.d/init/ycp-pdf.el @@ -57,6 +57,7 @@ "," #'my-pdf-view-shrink-a-bit "Q" #'my-pdf-dptrp1-upload ) + (my-setq-from-local my-pdf-dptrp1-ip) ) (my-package pdf-misc diff --git a/emacs/.emacs.d/init/ycp-web.el b/emacs/.emacs.d/init/ycp-web.el index b1e546c..7df2857 100644 --- a/emacs/.emacs.d/init/ycp-web.el +++ b/emacs/.emacs.d/init/ycp-web.el @@ -203,6 +203,7 @@ org-jira-jira-status-to-org-keyword-alist org-jira-project-filename-alist org-jira-custom-jqls) + (org-link-set-parameters "jira" '((:follow . org-jira-open))) (require 'my-org-jira) (my-override org-jira--render-issue) (my-override org-jira-update-worklogs-from-org-clocks) @@ -217,6 +218,9 @@ 'turn-off-flyspell) ) +(my-package dnd + (setq dnd-open-remote-file-function 'browse-url)) + (my-package eww (:delay 60) (advice-add 'eww-browse-url :filter-args #'my-rewrite-url-advice) @@ -245,7 +249,31 @@ "T" #'my-eww-top-path "b" #'my-eww-switch-by-title) (my-keybind global-map "\C-c\C-o" #'my-browse-url-at-point) - (my-override browse-url) + (my-setq-from-local my-newscorp-au-amp-nk) + (setq browse-url-handlers + `((exitter-post-url-p + . ,(lambda (url &rest _) (exitter-open-post url))) + (my-hacker-news-url-p + . ,(lambda (url &rest _) (hnreader-comment url))) + (my-gitlab-project-url-p + . ,(lambda (url &rest _) (my-gitlab-project-infobox url))) + (my-ytdl-video-url-p + . ,(lambda (url &rest _) (my-ytdl-video-infobox url))) + (my-mastodon-url-p + . ,(lambda (url &rest _) (mastorg-open url))) + (my-newscorp-au-url-p + . ,(lambda (url &rest _) (my-open-newscorp-au url))) + (my-org-jira-url-p + . ,(lambda (url &rest _) (my-org-jira-open-url url))) + (reddio-reddit-url-p + . ,(lambda (url &rest _) (reddio-open-url url))) + ("^https?://www.spectator.com.au\\>" . + ,(lambda (url &rest args) (my-open-spectator-au url))) + (my-stack-overflow-url-p + . ,(lambda (url &rest _) (sx-open-link url))) + (wiki-engine-entry-url-p + . ,(lambda (url &rest _) (wiki-open-url url))) + (stringp . browse-url-firefox))) ) (my-package my-semantic-scholar @@ -308,7 +336,8 @@ (my-setq-from-local my-libgen-hosts my-libgen-alt-hosts my-libgen-library-hosts my-libgen-onion-host ) - (setq my-libgen-download-dir my-document-incoming-dir) + (setq my-libgen-download-dir my-document-incoming-dir + my-libfic-download-dir my-document-incoming-dir) (my-libgen-set-random-hosts)) (my-package my-scihub @@ -334,7 +363,10 @@ exitter-oauth-consumer-key exitter-oauth-consumer-secret exitter-access-token exitter-username exitter-password exitter-email exitter-oauth-token exitter-oauth-token-secret exitter-oauth-token-ctime) - (setq exitter-debug t) + (setq exitter-debug nil) ) +(my-package reddio + (:delay 60)) + (provide 'ycp-web) diff --git a/emacs/.emacs.d/lisp/exitter b/emacs/.emacs.d/lisp/exitter -Subproject cdcda7feb9a5b9a4530be09149537217148b848 +Subproject e0aa1eb8b5dd2696f92f90348cb9e8aedd79800 diff --git a/emacs/.emacs.d/lisp/hmm.el b/emacs/.emacs.d/lisp/hmm.el -Subproject 2157ead39273691013c38529b14953ea839c2a5 +Subproject a0660da71f9aef8909973e9fd44b5eb34db0386 diff --git a/emacs/.emacs.d/lisp/mastodon.el b/emacs/.emacs.d/lisp/mastodon.el -Subproject d8ef4fff34862ff3cef76ea704b8c4c6c7d7508 +Subproject dbb1e5ef4473c418b164b4c74c44cf8ac95e4eb diff --git a/emacs/.emacs.d/lisp/my/infobox.el b/emacs/.emacs.d/lisp/my/infobox.el new file mode 100644 index 0000000..518c7db --- /dev/null +++ b/emacs/.emacs.d/lisp/my/infobox.el @@ -0,0 +1,145 @@ +;;; infobox.el -- Infobox in a help buffer -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "29.4")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Infobox in a help buffer. + +;;; Code: + + +(defun infobox-default-specs (info) + (seq-map + (lambda (pair) + (cons (car pair) + (replace-regexp-in-string + "[-_]" " " + (capitalize (format "%s" (car pair)))))) + info)) + +(defun infobox-translate (info specs) + "Translate INFO according to SPECS. + +TODO: allow multiple levels in specs keys using let-alist, i.e. +something like + +(.channel.name . \"Channel name\")" + (seq-map + (lambda (pair) + (when-let ((val (alist-get (car pair) info))) + (if (or (stringp (cdr pair)) (symbolp (cdr pair))) + (cons (cdr pair) val) + (cons (cadr pair) (funcall (cddr pair) val))))) + specs)) + +(defun infobox-render (info item &optional interactive-p) + "Render and display a help buffer of INFO." + (with-help-window "*infobox*" + (with-current-buffer standard-output + (let ((n-rows 0)) + (seq-do + (lambda (pair) + (when pair + (when (stringp (car pair)) + (insert (car pair) ": ") + (setq n-rows (1+ n-rows))) + (insert (format "%s" (cdr pair)) "\n"))) + info) + (align-regexp + (point-min) + (progn (goto-line (1+ n-rows)) (point)) + "\\(\\s-*\\):")) + (visual-line-mode))) + (with-current-buffer "*infobox*" + (let ((help-xref-following t)) + (help-setup-xref item interactive-p) + ))) + +(defun infobox-render-string (text item &optional interactive-p) + (help-setup-xref item interactive-p) + (with-help-window "*infobox*" + (with-current-buffer standard-output + (insert text) + (visual-line-mode))) + (with-current-buffer "*infobox*" + (let ((help-xref-following t)) + (help-setup-xref item interactive-p) + ))) + +(defun infobox-exiftool (filename) + (interactive (list (expand-file-name (read-file-name "infobox exiftool: ")))) + (infobox-render-string + (with-temp-buffer + (call-process "exiftool" nil t nil filename) + (buffer-string)) + `(infobox-exiftool ,filename) + (called-interactively-p 'interactive) + )) + +(defun infobox-pacman (package-name) + (interactive (list (completing-read + "pacman package: " + (infobox-pacman-installed-packages) + nil + t))) + (infobox-render-string + (with-temp-buffer + (call-process "pacman" nil t nil "-Qi" package-name) + (buffer-string)) + `(infobox-pacman ,package-name) + (called-interactively-p 'interactive) + )) + +(defun infobox-pacman-installed-packages () + "Returns list of installed packages." + (with-temp-buffer + (call-process "pacman" nil t nil "-Qq") + (split-string (buffer-string) "\n"))) + +(defun infobox-calibre (book-id) + (interactive (list (car (split-string + (completing-read + "calibre book: " + (infobox-calibre-books) + nil + t) + " ")))) + (infobox-render-string + (with-temp-buffer + (call-process "calibredb" nil t nil "show_metadata" book-id) + (buffer-string)) + `(infobox-calibre ,book-id) + (called-interactively-p 'interactive))) + +(defun infobox-calibre-books () + (with-temp-buffer + (call-process "calibredb" nil t nil "list") + (seq-filter + (lambda (line) (string-match-p "^[0-9]" line)) + (split-string (buffer-string) "\n")))) + +(defun my-call-process-out (command &rest args) + (with-temp-buffer + (apply 'call-process (append (list command nil t nil) args)) + (buffer-string))) + +(provide 'infobox) diff --git a/emacs/.emacs.d/lisp/my/my-buffer.el b/emacs/.emacs.d/lisp/my/my-buffer.el index f06956f..f2da7f5 100644 --- a/emacs/.emacs.d/lisp/my/my-buffer.el +++ b/emacs/.emacs.d/lisp/my/my-buffer.el @@ -239,24 +239,54 @@ that point." (setq buffer temp-buffer)) (set-window-buffer first-window buffer))) +(defun my-set-left-buffer () + "Generate and switch to an empty buffer." + (interactive) + (set-window-buffer + (window-left (get-buffer-window)) + (with-current-buffer (get-buffer-create "*my-left*") + (read-only-mode t) + (current-buffer)))) + +(defun my-set-right-buffer () + "Generate and switch to an empty buffer." + (interactive) + (set-window-buffer + (window-right (get-buffer-window)) + (with-current-buffer (get-buffer-create "*my-right*") + (read-only-mode t) + (current-buffer)))) + (defun my-toggle-focus-write () "Toggle focus write mode. Focus write: make the current window the only one centered with -width 80. If in org-mode, also narrow to current subtree." +width 80. If in org-mode, also narrow to current subtree. Make +buffers on both sides empty read-only buffers." (interactive) ;; Only one window in the current frame indicates we are in focus ;; write mode. - (if (length= (window-list) 1) + (if (and (equal + (buffer-name + (window-buffer (window-left (get-buffer-window)))) + "*my-left*") + (equal + (buffer-name + (window-buffer (window-right (get-buffer-window)))) + "*my-right*")) (progn (winner-undo) (when (derived-mode-p 'org-mode) (widen))) (when (derived-mode-p 'org-mode) (org-narrow-to-subtree)) - (delete-other-windows) - (let ((margin (/ (- (window-width) 80) 2))) - (set-window-margins nil margin margin)))) + (my-set-left-buffer) + (my-set-right-buffer) + (let ((margin (/ (- 80 (window-width)) 2))) + (enlarge-window margin t) + (windmove-left) + (enlarge-window (- margin) t) + (windmove-right)))) (defun my-select-new-window-matching-mode (mode) "Select a new window." @@ -415,6 +445,11 @@ for the given MAJOR-MODE, any text is appended to it." (4 (my-buffer-scratch-setup region default-mode)) (_ (my-buffer-scratch-setup region))))) +(defun my-new-empty-buffer () + "Generate and switch to an empty buffer." + (interactive) + (switch-to-buffer (generate-new-buffer "empty"))) + (defcustom my-scratch-buffer-default-mode 'org-mode "Default major mode for `my-buffer-create-scratch'." :type 'symbol @@ -483,5 +518,15 @@ With double prefix arguments, create a new indirect buffer." (revert-buffer t t)) (switch-to-buffer buffer))) +(defun my-fontify-with-mode (text mode) + "Fontify TEXT with MODE." + (with-temp-buffer + (funcall mode) + (insert text) + (if (fboundp 'font-lock-ensure) + (font-lock-ensure) + (with-no-warnings (font-lock-fontify-buffer))) + (buffer-string))) + (provide 'my-buffer) ;;; my-buffer.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-emms.el b/emacs/.emacs.d/lisp/my/my-emms.el index ffb6bc0..fd3c73d 100644 --- a/emacs/.emacs.d/lisp/my/my-emms.el +++ b/emacs/.emacs.d/lisp/my/my-emms.el @@ -165,7 +165,7 @@ either 'audio or 'video (if (and (length> players 1) (string-prefix-p "file://" name) (member (file-name-extension name) - '("mkv" "mp4" "ogv" "avi" "webm"))) + '("mkv" "ogv" "avi" "webm"))) 'emms-player-vlc 'emms-player-mpv))) @@ -300,10 +300,23 @@ filter extensions from filter-exts." (defvar my-emms-i3bar-file (locate-user-emacs-file "emms-i3bar") "File to write current playing to which i3bar reads") (defun my-emms-get-display-name (track) + "Return the display name of a track. + +The display name is either the info-title, or the display name of +the filename." (or (alist-get 'info-title track) (when-let ((name (alist-get 'name track))) - (replace-regexp-in-string "^\\(.*/\\)\\(.*/.*/.*\\)" "\\2" name)))) + (my-emms-get-display-name-1 name)))) + +(defun my-emms-get-display-name-1 (name) + "Return the display name of a filename NAME. + +The display name is the last three components of the filename, +assuming the filesystem hierarchy is arranged in +artist/album/track." + (replace-regexp-in-string "^\\(.*/\\)\\(.*/.*/.*\\)" "\\2" name)) + (defun my-emms-output-current-track-to-i3bar-file () (let ((current-track (my-emms-get-display-name (emms-playlist-current-selected-track)))) @@ -473,13 +486,40 @@ Hex-encoded characters in URLs are replaced by the decoded character." (let ((type (emms-track-type track))) (cond ((emms-track-get track 'description) - (emms-track-get track 'description)) - ((eq 'file type) - (emms-track-name track)) + (emms-track-get track 'description)) + ((eq 'file type) + (emms-track-name track)) ((eq 'url type) (emms-format-url-track-name (emms-track-name track))) (t (concat (symbol-name type) ": " (emms-track-name track)))))) +(defvar my-emms-score-delta 1) + +(defun my-emms-score-up-playing () + "Increase score by `my-emms-score-delta', then reset it to 1." + (emms-score-change-score + my-emms-score-delta + (my-emms-get-display-name-1 (emms-score-current-selected-track-filename))) + (setq my-emms-score-delta 1)) + +(defun my-emms-score-up-chosen-bonus () + "Bonus score up if the track is started intentionally. + +If the last command is `emms-playlist-mode-play-smart', then set +`my-emms-score-delta' to 2." + (when (eq last-command 'emms-playlist-mode-play-smart) + (setq my-emms-score-delta 2))) + +(defun my-emms-wrapped () + "Print top 5 scored tracks." + (interactive) + (let (keys) + (maphash (lambda (k _) (push k keys)) emms-score-hash) + (sort keys (lambda (k1 k2) + (> (cl-second (gethash k1 emms-score-hash)) + (cl-second (gethash k2 emms-score-hash))))) + (message "Top 5: %s" (string-join (take 5 keys) "\n")))) + (provide 'my-emms) ;;; my-emms.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-github.el b/emacs/.emacs.d/lisp/my/my-github.el index 45adcf6..1643612 100644 --- a/emacs/.emacs.d/lisp/my/my-github.el +++ b/emacs/.emacs.d/lisp/my/my-github.el @@ -54,6 +54,56 @@ License; name; description; homepage; created at" (cons "Developers" (my-grok-github-get-developer-name (alist-get 'url (alist-get 'owner raw)))))) +(defun my-github-api-repos (url) + (when-let* ((urlobj (url-generic-parse-url url)) + (path (url-filename urlobj)) + (project-id + (when (string-match "^/[^/]+/[^/]+" path) + (match-string 0 path)))) + (my-url-fetch-json + (format "https://api.github.com/repos%s" project-id)))) + +(defun my-github-api-readme (url) + (when-let* ((urlobj (url-generic-parse-url url)) + (path (url-filename urlobj)) + (project-id + (when (string-match "^/[^/]+/[^/]+" path) + (match-string 0 path))) + ;; so that the response of readme is in html format + (url-request-extra-headers + '(("Accept" . "application/vnd.github.html")))) + (my-url-fetch-raw + (format "https://api.github.com/repos%s/readme" project-id)))) + +(defun my-github-project-infobox (url) + (interactive "sGithub repo url: ") + (let ((info + (append + (my-github-api-repos url) + `((readme . ,(my-github-api-readme url)))))) + (infobox-render + (infobox-translate + info my-github-project-info-specs) + `(my-github-project-infobox ,url) + (called-interactively-p 'interactive))) + ) + +(defvar my-github-project-info-specs + `((html_url . "Clone") + (full_name . "Name") + (description . "Description") + (created_at . ("Created at" . my-gitlab-format-time-string)) + (pushed_at . ("Pushed at" . my-gitlab-format-time-string)) + (topics . ("Topics" . ,(lambda (xs) + (mapconcat #'identity xs "; ")))) + (stargazers_count . ("Stars" . number-to-string)) + (forks_count . ("Forks" . number-to-string)) + (readme . (body . ,(lambda (text) + (with-temp-buffer + (insert text) + (shr-render-region (point-min) (point-max)) + (buffer-string))))))) + (defun my-grok-github-get-developer-name (url) (with-current-buffer (url-retrieve-synchronously url) (set-buffer-multibyte t) diff --git a/emacs/.emacs.d/lisp/my/my-gitlab.el b/emacs/.emacs.d/lisp/my/my-gitlab.el index 6dd484c..ad7f0ed 100644 --- a/emacs/.emacs.d/lisp/my/my-gitlab.el +++ b/emacs/.emacs.d/lisp/my/my-gitlab.el @@ -26,8 +26,9 @@ ;;; Code: +(require 'infobox) -(defun my-get-gitlab-project-id (url) +(defun my-gitlab-get-project-id (url) (with-current-buffer (url-retrieve-synchronously (replace-regexp-in-string "\\.git$" "" url)) (let ((dom (libxml-parse-html-region (point-min) (point-max)))) @@ -35,16 +36,75 @@ (dom-search dom (lambda (n) (dom-attr n 'data-project-id)))) 'data-project-id)))) -(defun my-grok-gitlab (url) +(defun my-gitlab-api-projects (url) (when-let* ((urlobj (url-generic-parse-url url)) - (project-id (my-get-gitlab-project-id url))) - (with-current-buffer - (url-retrieve-synchronously - (concat (url-type urlobj) "://" (url-host urlobj) - "/api/v4/projects/" project-id)) - (set-buffer-multibyte t) - (my-delete-http-header) - (my-grok-gitlab-make-info (json-read))))) + (project-id (my-gitlab-get-project-id url))) + (my-url-fetch-json + (format "%s://%s/api/v4/projects/%s" + (url-type urlobj) + (url-host urlobj) + project-id)))) + +(defvar my-gitlab-readme-get-raw nil "Whether to get raw or html readme") + +(defun my-gitlab-project-info (url) + "Given a url, returns project info." + (let ((info (my-gitlab-api-projects url))) + (let-alist info + (when .readme_url + (setf (alist-get 'readme info) + (if my-gitlab-readme-get-raw + (format + "\n%s" + (my-url-fetch-raw + (replace-regexp-in-string "/-/blob/" "/-/raw/" .readme_url))) + (alist-get + 'html + (my-url-fetch-json + (format "%s?format=json&viewer=rich" .readme_url))))))) + info)) + +(defun my-gitlab-format-time-string (t) + (format-time-string "%Y-%m-%d %M:%M:%S" (encode-time (parse-time-string t)))) + +(defun my-gitlab-project-url-p (url) + (let ((urlobj (url-generic-parse-url url))) + (and (equal (url-host urlobj) "gitlab.com") + (string-match-p "^/[^/]+/[^/]+$" (url-filename urlobj))))) + +(require 'my-buffer) + +(defvar my-gitlab-project-info-specs + `((http_url_to_repo . "Clone") + (name_with_namespace . "Name") + (description . "Description") + (created_at . ("Created at" . my-gitlab-format-time-string)) + (last_activity_at . ("Updated at" . my-gitlab-format-time-string)) + (topics . ("Topics" . ,(lambda (xs) + (mapconcat #'identity xs "; ")))) + (star_count . ("Stars" . number-to-string)) + (forks_count . ("Forks" . number-to-string)) + (readme . (body . ,(lambda (text) + (with-temp-buffer + (insert text) + (shr-render-region (point-min) (point-max)) + (buffer-string))))))) + +(defun my-gitlab-project-infobox (url) + "Display a gitlab project info at URL in a help buffer. + +A good example would be +<https://gitlab.com/woob/woob> +" + (interactive "sGitlab project URL: ") + (infobox-render + (infobox-translate + (my-gitlab-project-info url) my-gitlab-project-info-specs) + `(my-gitlab-project-infobox ,url) + (called-interactively-p 'interactive))) + +(defun my-grok-gitlab (url) + (my-grok-gitlab-make-info (my-gitlab-api-projects url))) (defun my-grok-gitlab-make-info (raw) (list (cons "Title" (alist-get 'name raw)) diff --git a/emacs/.emacs.d/lisp/my/my-ledger.el b/emacs/.emacs.d/lisp/my/my-ledger.el new file mode 100644 index 0000000..b1ad2ca --- /dev/null +++ b/emacs/.emacs.d/lisp/my/my-ledger.el @@ -0,0 +1,52 @@ +;;; my-ledger.el -- customizations to ledger mode -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "29.4")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; customizations to ledger mode. + +;;; Code: + + +(defun my-ledger-move-xact-down () + (interactive) + (call-interactively 'transpose-paragraphs) + (call-interactively 'ledger-navigate-prev-xact-or-directive)) + +(defun my-ledger-move-xact-up () + (interactive) + (call-interactively 'ledger-navigate-prev-xact-or-directive) + (call-interactively 'transpose-paragraphs) + (call-interactively 'ledger-navigate-prev-xact-or-directive) + (call-interactively 'ledger-navigate-prev-xact-or-directive)) + +;;; hledger: Error: /home/ycp/Documents/finance/huecu.ledger:1615:41: +(defvar my-ledger-compilation-error-re + '(ledger "^hledger: Error: \\(.+\\):\\([0-9]+\\):\\([0-9]+\\):$" 1 2 3)) + +(defun my-ledger-set-compile-command () + (setq-local + compile-command + (format "%s bal -f %s" ledger-binary-path buffer-file-name))) + +(provide 'my-ledger) +;;; my-ledger.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-libgen.el b/emacs/.emacs.d/lisp/my/my-libgen.el index 6b65eb1..67e0071 100644 --- a/emacs/.emacs.d/lisp/my/my-libgen.el +++ b/emacs/.emacs.d/lisp/my/my-libgen.el @@ -251,5 +251,121 @@ (filesize-human . ,filesize-human) (extension . ,extension)))) +(defvar my-libfic-download-dir "~/Downloads") +(defun my-libfic-search (query) + (interactive "sQuery: ") + (generic-search-open + (mapcar 'my-libfic-search-parse-tr + (cdr + (dom-by-tag + (my-url-fetch-dom + (format "%s/fiction/?q=%s" + my-libgen-host query)) + 'tr))) + (format "libfic-query:%s" query) + `((formatter . my-libfic-search-format-result) + (default-action . my-grok-libfic-action) + (keymap . ,my-libfic-button-keymap)))) + +(defun my-libfic-search-parse-tr (tr) + (let* ((tds (dom-by-tag tr 'td)) + (author (string-trim (dom-texts (pop tds) ""))) + (series (dom-text (pop tds))) + (title-id (pop tds)) + (title-md5 (car (dom-by-tag title-id 'a))) + (title (dom-text title-md5)) + (md5 (elt (split-string (or (dom-attr title-md5 'href) "") "/") 2)) + (identifier (dom-text (dom-by-class title-id "catalog_identifier"))) + (language (dom-text (pop tds))) + (extension-filesize-human (split-string (dom-text (pop tds)) " / ")) + (extension (downcase (car extension-filesize-human))) + (filesize-human (cadr extension-filesize-human)) + ) + `((author . ,author) + (series . ,series) + (md5 . ,md5) + (title . ,title) + (identifier . ,identifier) + (language . ,language) + (filesize-human . ,filesize-human) + (extension . ,extension)))) + +(defun my-libfic-search-format-result (info) + (format + "%s [%s] %s" + (my-libfic-format-filename info) + (alist-get 'language info) + (alist-get 'filesize-human info))) + +(defun my-libfic-format-filename (info) + (replace-regexp-in-string "[:;]" "_" + (format + "%s - %s (%s) [%s].%s" + (alist-get 'author info) + (alist-get 'title info) + (alist-get 'series info) + (alist-get 'identifier info) + (alist-get 'extension info)))) + +(defun my-grok-libfic-action (info) + (interactive) + (my-org-create-node + (my-grok-libfic-make-info + (my-libfic-update-info info)) + t)) + +(defun my-libfic-update-info (info) + (when-let ((tr-id + (seq-find + (lambda (tr) + (equal "ID:" (dom-text (car (dom-by-tag tr 'td))))) + (dom-by-tag + (my-url-fetch-dom + (format "%s/fiction/%s" my-libgen-host (alist-get 'md5 info))) + 'tr)))) + `((id . ,(dom-text (cadr (dom-by-tag tr-id 'td)))) . ,info))) + +;;; todo: description; publisher; cover +(defun my-grok-libfic-make-info (info) + (list + (cons "libfic-id" (alist-get 'id info)) + (cons "Title" (alist-get 'title info)) + (cons "Series" (alist-get 'series info)) + (cons "Authors" (alist-get 'author info)) + (cons "ISBN" (alist-get 'identifier info)) + (cons "Language" (alist-get 'language info)) + (cons "Filesize-human" (alist-get 'filesize-human info)) + (cons "Extension" (alist-get 'extension info)) + (cons "md5" (alist-get 'md5 info)))) + +(defvar my-libfic-button-keymap + (let ((kmap (make-sparse-keymap))) + (set-keymap-parent kmap button-map) + (define-key kmap "d" 'my-libfic-download-action) + (define-key kmap "p" 'my-libfic-show-more-info) + kmap)) + +(defun my-libfic-show-more-info () + (interactive) + (let ((info (get-text-property (point) 'button-data))) + (pp (my-grok-libfic-make-info (my-libfic-update-info info))))) + +(defun my-libfic-download-action () + (interactive) + (let ((info (get-text-property (point) 'button-data))) + (my-wget-async + (my-libfic-make-download-link-onion + (my-libfic-update-info info)) + (format "%s/%s" (expand-file-name my-libfic-download-dir) + (my-libfic-format-filename info))))) + +(defun my-libfic-make-download-link-onion (info) + (let ((id-head (substring (alist-get 'id info) 0 -3))) + (format "%s/FF/%s%s/%s" + my-libgen-onion-host + (make-string (- 4 (length id-head)) ?0) + id-head + (downcase (alist-get 'md5 info))))) + (provide 'my-libgen) ;;; my-libgen.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-mariadb.el b/emacs/.emacs.d/lisp/my/my-mariadb.el index 6b0e06b..bdb1c60 100644 --- a/emacs/.emacs.d/lisp/my/my-mariadb.el +++ b/emacs/.emacs.d/lisp/my/my-mariadb.el @@ -251,6 +251,24 @@ enum spider_malloc_id { nil t) (tempel-insert 'ps))) +(defun my-mariadb-kb-url-p (url) + (string-match-p "https://mariadb.com/kb/en/\\([^/]+\\)/" url)) + +(defun my-wiki-mariadb-extract-kb-source () + "Extract the kb source from the current buffer. + +Used for wiki mode as a post-processor." + (let ((source + (dom-text + (dom-by-id + (libxml-parse-html-region (point-min) (point-max)) + "answer_source")))) + (erase-buffer) + (insert source)) + (goto-char (point-min)) + (save-buffer) + ) + (defun my-mariadb-fetch-kb-source (url) "Fetches the source to an maridb kb entry at URL. diff --git a/emacs/.emacs.d/lisp/my/my-net.el b/emacs/.emacs.d/lisp/my/my-net.el index 1f1cbc6..2574789 100644 --- a/emacs/.emacs.d/lisp/my/my-net.el +++ b/emacs/.emacs.d/lisp/my/my-net.el @@ -119,6 +119,14 @@ It checks the STATUS, and if it is ok, saves the payload to FILE-NAME." decompression with-header)) + +(defun my-url-fetch-raw (url &optional decompression with-header) + (my-url-fetch-internal + url + (lambda () (decode-coding-string (buffer-string) 'utf-8)) + decompression + with-header)) + (defun my-url-fetch-internal (url buffer-processor decompression with-header) (with-current-buffer (get-buffer-create my-client-buffer-name) (goto-char (point-max)) @@ -141,7 +149,7 @@ It checks the STATUS, and if it is ok, saves the payload to FILE-NAME." (list (cons 'header fields) (cons 'json (funcall buffer-processor))) - (funcall buffer-processor))) + (when buffer-processor (funcall buffer-processor)))) (error "HTTP error: %s" (buffer-substring (point) (point-max))))))) (provide 'my-net) diff --git a/emacs/.emacs.d/lisp/my/my-nov.el b/emacs/.emacs.d/lisp/my/my-nov.el index 863d09a..1bc8eca 100644 --- a/emacs/.emacs.d/lisp/my/my-nov.el +++ b/emacs/.emacs.d/lisp/my/my-nov.el @@ -52,5 +52,21 @@ chapter title." (nov-next-document) (follow-scroll-up arg))) +(defun my-nov-copy-buffer-file-with-staging () + (interactive) + (unless (derived-mode-p 'nov-mode) (error "Not in nov mode")) + (pcase-let* ((name + (completing-read (format "Copy %s to: " nov-file-name) + my-copy-file-targets + nil t)) + (`(,dest ,staging) (alist-get name my-copy-file-targets + nil nil #'equal))) + (my-copy-file-with-staging + nov-file-name dest staging))) + +(defun my-nov-set-margins () + (set-window-margins nil 3 2) + (set-window-fringes nil 0 0)) + (provide 'my-nov) ;;; my-nov.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-org-jira.el b/emacs/.emacs.d/lisp/my/my-org-jira.el index 2502f02..9e2f821 100644 --- a/emacs/.emacs.d/lisp/my/my-org-jira.el +++ b/emacs/.emacs.d/lisp/my/my-org-jira.el @@ -82,7 +82,7 @@ :proj-key (path '(fields project key)) :related-issues (mapconcat (lambda (c) - (print c) + ;; (print c) (if (org-jira-sdk-path c '(inwardIssue)) (if (equal (org-jira-sdk-path @@ -269,5 +269,13 @@ (interactive) (kill-new (my-org-jira-comment-url-at-point))) +(defun my-org-jira-url-p (url) + (string-match-p (format "^%s/browse/[^/]" jiralib-url) url)) + +(defun my-org-jira-open-url (url) + (interactive "sJIRA issue url: ") + (when (string-match (format "^%s/browse/\\([^/]+\\)" jiralib-url) url) + (org-jira-get-issue (match-string 1 url)))) + (provide 'my-org-jira) ;;; my-org-jira.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-org-remark.el b/emacs/.emacs.d/lisp/my/my-org-remark.el new file mode 100644 index 0000000..3e0ef0a --- /dev/null +++ b/emacs/.emacs.d/lisp/my/my-org-remark.el @@ -0,0 +1,36 @@ +;;; my-org-remark.el -- customization to org-remark -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "29.4")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; customization to org-remark. + +;;; Code: + +(defun my-org-remark-open-or-create () + (interactive) + (if mark-active + (call-interactively 'org-remark-mark) + (call-interactively 'org-remark-open))) + +(provide 'my-org-remark) +;;; my-org-remark.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-org.el b/emacs/.emacs.d/lisp/my/my-org.el index ad0c3cb..5d7203f 100644 --- a/emacs/.emacs.d/lisp/my/my-org.el +++ b/emacs/.emacs.d/lisp/my/my-org.el @@ -81,7 +81,12 @@ buffer was a live window.") (defun my-org-edit-src-before-exit () "A :before advice for org-edit-src-exit." - (delete-trailing-whitespace) + (goto-char (point-min)) + (and + (>= (skip-chars-forward "\n") 1) + (region-modifiable-p (point-min) (point)) + (delete-region (point-min) (point))) + (let ((delete-trailing-lines t)) (delete-trailing-whitespace)) (setq my-org-edit-src-was-live-window (get-buffer-window (current-buffer)))) (defun my-org-element-block-p (element) @@ -812,8 +817,6 @@ When BLOCK-REGEXP is non-nil, use this regexp to find blocks." (cl-letf (((symbol-function 'delete-other-windows) 'ignore)) (apply oldfun args))) -(defvar my-org-attach-copy-attached-targets nil - "Alist of targets to copy attached to, in the form of (name . path)") (defvar my-org-attach-copy-attached-doc-exts '("epub" "pdf" "mobi")) (defvar my-org-attach-copy-attached-doc-re @@ -832,44 +835,15 @@ On success, also move everything from staging to to-dir." (interactive) (pcase-let* ((name (completing-read "Copy attached docs to: " - my-org-attach-copy-attached-targets + my-copy-file-targets nil t)) - (`(,to ,staging) (alist-get name my-org-attach-copy-attached-targets + (`(,to ,staging) (alist-get name my-copy-file-targets nil nil #'equal))) - (let ((basedir (org-attach-dir)) - (failed nil)) - (dolist (attached (org-attach-file-list basedir)) - (when (string-match my-org-attach-copy-attached-doc-re attached) - (message "Copying %s to %s (%s)..." attached name to) - (condition-case nil - (copy-file (file-name-concat basedir attached) - (file-name-concat - to - (replace-regexp-in-string ":" "_" attached)) - t) - (error - (message "Hardlinking %s to %s staging area (%s)" - attached name staging) - (setq failed t) - (add-name-to-file - (file-name-concat basedir attached) - (file-name-concat - staging - (replace-regexp-in-string ":" "_" attached)) - t))) - (message "Done!"))) - (unless failed - (dolist (staged - (directory-files staging nil - my-org-attach-copy-attached-doc-re)) - (message "Moving staged %s to %s (%s)..." staged name to) - (copy-file (file-name-concat staging staged) - (file-name-concat - to - (replace-regexp-in-string ":" "_" staged)) - t) - (delete-file (file-name-concat staging staged)) - (message "Done!")))))) + (my-copy-files-with-staging + (directory-files-recursively (org-attach-dir) + my-org-attach-copy-attached-doc-re) + to + staging))) (defun my-org-attach-all-url-plaintext (arg) (interactive "P") @@ -1088,6 +1062,11 @@ On success, also move everything from staging to to-dir." (org-protocol-grok (list :url (plist-get eww-data :url)))) +(defun my-org-protocol-browse-url (data) + (when-let ((url (plist-get data :url))) + (browse-url url)) + nil) + ;; org capture rss (defun my-org-rss-xml-create-audio-node (url) (interactive (list (read-string "Feed URL: " @@ -1365,7 +1344,7 @@ With a prefix arg, yank and exit immediately." (org-edit-src-exit)))) ;; used to add an :after advice to `org-edit-special'. -(defun my-org-edit-special-after () +(defun my-org-edit-special-after (&rest _) ;; some modes (e.g. diff mode) are read-only by default, which ;; does not make sense when the intention is to edit (read-only-mode 0)) diff --git a/emacs/.emacs.d/lisp/my/my-prog.el b/emacs/.emacs.d/lisp/my/my-prog.el index 396d919..a81d36d 100644 --- a/emacs/.emacs.d/lisp/my/my-prog.el +++ b/emacs/.emacs.d/lisp/my/my-prog.el @@ -419,7 +419,7 @@ overlay arrow in source buffer." ;; (gdb-input (concat "complete " context command) ;; (lambda () (setq gud-gdb-fetch-lines-in-progress nil))) ;; (while gud-gdb-fetch-lines-in-progress -;; (accept-process-output (get-buffer-process gud-comint-buffer) 1))) +;; (accept-process-output (get-buffer-process gud-comint-buffer) .1))) ;; (gud-gdb-completions-1 gud-gdb-fetched-lines))) ;;; which-func diff --git a/emacs/.emacs.d/lisp/my/my-utils.el b/emacs/.emacs.d/lisp/my/my-utils.el index bc200c2..3ecd0a9 100644 --- a/emacs/.emacs.d/lisp/my/my-utils.el +++ b/emacs/.emacs.d/lisp/my/my-utils.el @@ -321,7 +321,7 @@ Example: (format-time-string ... (my-time-from-epoch 1698582504))" (defvar my-extension-types '((audio . ("asf" "cue" "flac" "m4a" "m4r" "mid" "mp3" "ogg" "opus" - "wav" "wma" "spc")) + "wav" "wma" "spc" "mp4")) (video . ("avi" "m4v" "mkv" "mp4" "mpg" "ogg" "ogv" "rmvb" "webm" "wmv")))) ;;; files @@ -332,6 +332,75 @@ Example: (format-time-string ... (my-time-from-epoch 1698582504))" (make-symbolic-link newname file ok-if-already-exists) newname) +(defvar my-copy-file-targets nil + "Alist of targets to copy attached to, in the form of (name dest staging)") + +(defun my-copy-buffer-file-with-staging () + (interactive) + (unless (buffer-file-name) (error "buffer-file-name is nil")) + (pcase-let* ((name + (completing-read (format "Copy %s to: " (buffer-file-name)) + my-copy-file-targets + nil t)) + (`(,dest ,staging) (alist-get name my-copy-file-targets + nil nil #'equal))) + (my-copy-file-with-staging + (buffer-file-name) dest staging))) + +(defun my-flush-staging-files (staging dest) + "Flush files from STAGING to DEST." + (dolist (staged (directory-files staging)) + (unless (file-directory-p (file-name-concat staging staged)) + (message "Moving staged %s to %s..." staged dest) + (copy-file (file-name-concat staging staged) + (file-name-concat dest staged) + t) + (delete-file (file-name-concat staging staged))))) + +(defun my-flush-staging-files-x () + (interactive) + (pcase-let* ((name + (completing-read (format "Copy %s to: " (buffer-file-name)) + my-copy-file-targets + nil t)) + (`(,dest ,staging) (alist-get name my-copy-file-targets + nil nil #'equal))) + (my-flush-staging-files staging dest))) + +(defun my-copy-file-with-staging (src dest staging) + "Copy a file SRC to DEST with fallback to hardlinking to STAGING." + (my-copy-files-with-staging (list src) dest staging)) + +(defun my-copy-files-with-staging (src dest staging) + "Copy a list of file SRC to DEST with staging. + +DEST and STAGING should be directories. +On failure, hard link to STAGING. +On success, also move everything from STAGING to DEST." + (cl-assert (listp src)) + (let (failed) + (dolist (file src) + (cond + ((not failed) + (message "Copying %s to %s..." file dest) + (condition-case err + (copy-file + file (file-name-concat dest (file-name-nondirectory file)) t) + (error + (message "Encountered error while copying: %s" + (error-message-string err)) + (message "Hardlinking instead %s to staging area %s" src staging) + (setq failed t) + (add-name-to-file + file (file-name-concat staging (file-name-nondirectory file)) t)))) + (t + (message "Hardlinking %s staging area %s" src staging) + (add-name-to-file + file (file-name-concat staging (file-name-nondirectory file)) t)))) + (unless failed + (my-flush-staging-files staging dest)) + (message "Done!"))) + (defun my-rewrite-url-advice (args) (let ((url (car args))) (setcar args (my-rewrite-url url))) diff --git a/emacs/.emacs.d/lisp/my/my-web.el b/emacs/.emacs.d/lisp/my/my-web.el index 311bcf9..f2e48ba 100644 --- a/emacs/.emacs.d/lisp/my/my-web.el +++ b/emacs/.emacs.d/lisp/my/my-web.el @@ -86,19 +86,6 @@ (start-process (concat "mullvad-browser " url) nil "mullvad-browser" url)) -;; TODO: change to using hmm matching url with default app -;; override browse-url -(defun my-browse-url (url &optional arg) - (interactive "P") - (cond ((equal arg '(4)) - (funcall browse-url-secondary-browser-function url)) - ((equal arg '(16)) - (my-browse-url-tor-browser url)) - (t (luwak-open url)))) - -;; this fixes clicking url buttons like those in gnus messages -(defalias 'browse-url-button-open-url 'my-browse-url) - (defun my-browse-url-at-point (arg) (interactive "P") (my-browse-url (browse-url-url-at-point) arg)) @@ -148,5 +135,68 @@ (kill-new url) (message "Copied link: %s" url))) +;;; webgetter +(require 'my-net) +(defun my-open-spectator-au (url &optional no-overwrite) + (interactive "sspectator.com.au link: ") + (let ((url-request-extra-headers '(("X-Forwarded-For" . "66.249.66.1"))) + (url-user-agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")) + (let ((file-name + (if no-overwrite + (my-make-unique-file-name + (my-make-file-name-from-url url) + my-download-dir) + (expand-file-name + (my-make-file-name-from-url url) + my-download-dir)))) + (url-copy-file url file-name (not no-overwrite)) + (browse-url-firefox (format "file://%s" file-name))))) + +(defun my-mastodon-url-p (url) + "Guess if a url is a mastodon post. +e.g. https://hostux.social/@fsf/113709722998924141 +" + (pcase-let* ((urlobj (url-generic-parse-url url)) + (`(,path . _) (url-path-and-query urlobj))) + (string-match-p "^/@[^/]+/[0-9]\\{18\\}$" path))) + +(defun my-hacker-news-url-p (url) + "Check if a url is a hacker news post. +e.g. https://news.ycombinator.com/item?id=42505454" + (let ((urlobj (url-generic-parse-url url))) + (and (equal "news.ycombinator.com" (url-host urlobj)) + (string-match-p "^/item\\?id=[0-9]+$" (url-filename urlobj))))) + +(defvar my-newscorp-au-amp-nk nil) +(defun my-open-newscorp-au (url) + (interactive "sNews Corp AU link: ") + (pcase-let* ((urlobj (url-generic-parse-url url)) + (`(,path . _) (url-path-and-query urlobj))) + (setf (url-filename urlobj) + (format "%s?amp&nk=%s" path my-newscorp-au-amp-nk)) + (browse-url-firefox (url-recreate-url urlobj)))) + +(defun my-newscorp-au-url-p (url) + (string-match-p "^\\(www\\.\\)?\\(heraldsun\\|theaustralian\\)\\.com\\.au$" + (url-host (url-generic-parse-url url)))) + +(defun my-stack-overflow-url-p (url) + "Guess whether a url stack overflow question +e.g. +https://emacs.stackexchange.com/questions/40887/in-org-mode-how-do-i-link-to-internal-documentation" + (pcase-let* ((urlobj (url-generic-parse-url url)) + (`(,path . _) (url-path-and-query urlobj))) + (string-match-p "^/questions/[0-9]+/.+$" path)) ) + +(advice-add 'server-visit-files :around #'my-ec-handle-http) +(defun my-ec-handle-http (orig-fun files client &rest args) + ;; (message "GOT %s" files) + (dolist (var files) + (let ((fname (expand-file-name (car var)))) + (when (string-match ".*/?\\(https?:\\)/+" fname) + (browse-url (replace-match "\\1//" nil nil fname)) + (setq files (delq var files))))) + (apply orig-fun files client args)) + (provide 'my-web) ;;; my-web.el ends here diff --git a/emacs/.emacs.d/lisp/my/my-ytdl.el b/emacs/.emacs.d/lisp/my/my-ytdl.el index 9118493..2811793 100644 --- a/emacs/.emacs.d/lisp/my/my-ytdl.el +++ b/emacs/.emacs.d/lisp/my/my-ytdl.el @@ -76,6 +76,33 @@ (if (eq type 'video) my-ytdl-video-args my-ytdl-audio-args) (split-string urls))))) +(defun my-ytdl-video-info (url) + "Given a video URL, return an alist of its properties." + (with-temp-buffer + (call-process my-ytdl-program nil t nil "--no-warnings" "-j" url) + (let ((start (point))) + (call-process-region + nil nil "jq" nil t nil + "pick(.webpage_url, .fulltitle, .channel_url, .channel, .channel_follower_count, .thumbnail, .duration_string, .view_count, .upload_date, .like_count, .is_live, .was_live, .categories, .tags, .chapters, .availability, .uploader, .description)") + (goto-char start) + (json-read))) + ) + +(defun my-ytdl-video-url-p (url) + (let ((urlobj (url-generic-parse-url url))) + (or (and (string-match-p "^\\(www\\.\\)?youtube.com" (url-host urlobj)) + (string-match-p "^/watch\\?v=.*" (url-filename urlobj))) + (equal "youtu.be" (url-host urlobj))))) + +(defun my-ytdl-video-infobox (url) + (interactive "sytdl video url: ") + (let* ((info (my-ytdl-video-info url)) + (specs (infobox-default-specs info))) + (infobox-render + (infobox-translate info specs) + `(my-ytdl-video-infobox ,url) + (called-interactively-p 'interactive)))) + ;;; fixme: autoload (defun my-ytdl-video (urls) "Download videos with ytdl." diff --git a/emacs/.emacs.d/lisp/my/reddio.el b/emacs/.emacs.d/lisp/my/reddio.el new file mode 100644 index 0000000..2198e43 --- /dev/null +++ b/emacs/.emacs.d/lisp/my/reddio.el @@ -0,0 +1,53 @@ +;;; reddio.el -- reddit client through reddio -*- lexical-binding: t -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "29.4")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; reddit client through reddio. + +;;; Code: + +(defvar reddio-buffer "*reddio*") + +(defun reddio-open-url (url) + (interactive "sReddit link: ") + (when (string-match "/\\(comments/[^/]+\\)/" url) + (with-current-buffer (get-buffer-create reddio-buffer) + (let ((inhibit-read-only t)) + (erase-buffer) + (when (= 0 (call-process "reddio" nil reddio-buffer nil "print" + (match-string 1 url))) + (goto-char (point-min))) + (delete-trailing-whitespace)) + (text-mode) + (view-mode)) + (display-buffer reddio-buffer))) + +(defun reddio-reddit-url-p (url) + "e.g. +https://www.reddit.com/r/linux/comments/cs3os6/introducing_reddio_a_commandline_interface_for/" + (let ((urlobj (url-generic-parse-url url))) + (and (string-match-p "^.*\\<reddit.com$" (url-host urlobj)) + (string-match-p "^/r/[^/]+/comments/[^/]+/.+$" (url-filename urlobj))))) + +(provide 'reddio) +;;; reddio.el ends here diff --git a/emacs/.emacs.d/lisp/nov.el b/emacs/.emacs.d/lisp/nov.el -Subproject b3c7cc28e95fe25ce7b443e5f49e2e45360944a +Subproject c0d30da504fb0b68d8c28ff61a5e0095acda7f5 diff --git a/emacs/.emacs.d/lisp/wiki.el b/emacs/.emacs.d/lisp/wiki.el -Subproject e501c186bccd76a2373977b3df59300fe390bd6 +Subproject 3bb836e703480e23b3eee8fdb369dacb294dc46 |