;;; pkg-social.el --- Social media and security tools -*- lexical-binding: t -*-
;; Copyright (C) 2026 Cytrogen
;; This file contains:
;; - Mastodon client configuration
;; - GPG/EPA security setup
;; - Social media integrations
;;; Commentary:
;; Configuration for social media tools and security features.
;; Includes comprehensive GPG setup with Windows compatibility.
;;; Code:
;; Dependencies
(require 'cl-lib) ; 确保cl-some函数可用
;; GPG Configuration
;; Cross-platform GPG setup with error handling
(defun my/setup-gpg ()
"配置GPG,优先查找Windows常见安装路径。"
(interactive)
(require 'epa-file)
;; 1. 定义查找列表:优先找用户实际安装位置
(let* ((candidates '("D:/GnuPG/bin/gpg.exe" ; 用户实际安装位置 (最高优先级)
"C:/Program Files (x86)/GnuPG/bin/gpg.exe" ; Gpg4win 默认路径 (64位系统)
"C:/Program Files/GnuPG/bin/gpg.exe" ; Gpg4win 备选路径
"gpg")) ; 系统 PATH
;; 找到第一个存在的路径
(found-gpg (cl-some (lambda (path)
(if (string-match-p "/" path)
(and (file-exists-p path) path)
(executable-find path)))
candidates)))
(if found-gpg
(progn
;; Add GPG path to exec-path for proper discovery
(let ((gpg-dir (file-name-directory found-gpg)))
(when (and gpg-dir (not (member gpg-dir exec-path)))
(add-to-list 'exec-path gpg-dir)))
;; Use custom-set-variables for EPA configuration (StackOverflow solution)
(custom-set-variables
`(epg-gpg-program ,found-gpg)
'(epa-pinentry-mode 'loopback)
'(epg-debug t)
`(epg-gpg-home-directory ,(or (getenv "GNUPGHOME")
(expand-file-name "~/.gnupg")))
'(epa-file-cache-passphrase-for-symmetric-encryption t)
'(epa-file-select-keys 'silent)
'(epa-file-encrypt-to nil)
'(epa-armor t)
'(epg-user-id nil))
;; Enable encrypted file support
(require 'epa-file)
(epa-file-enable)
(message "GPG configured successfully. Path: %s" found-gpg))
;; GPG not found handling
(message "Warning: GPG executable not found. Mastodon will not be able to save credentials securely.")
(message "Please check if Gpg4win is installed and confirm the installation path."))))
;; GPG Management Tools
(defun my/check-or-create-gpg-keys ()
"检测GPG密钥,如果没有则提示创建。"
(interactive)
(when epg-gpg-program
(condition-case err
(let* ((context (epg-make-context))
(keys (epg-list-keys context)))
(if keys
(progn
(message "Found %d GPG keys" (length keys))
(dolist (key keys)
(let ((user-ids (epg-key-user-id-list key)))
(when user-ids
(message "Key: %s" (epg-user-id-string (car user-ids)))))))
;; 没有密钥,提供创建选项
(when (yes-or-no-p "未找到GPG密钥,是否创建新密钥对? ")
(my/create-gpg-keypair))))
(error
(message "Error checking GPG keys: %s" (error-message-string err))))))
(defun my/create-gpg-keypair ()
"创建新的GPG密钥对。"
(interactive)
(let* ((name (read-string "输入姓名: "))
(email (read-string "输入邮箱: "))
(passphrase (read-passwd "输入密码(可选,直接回车跳过): "))
(batch-config (format "%%echo Generating GPG key
Key-Type: RSA
Key-Length: 2048
Subkey-Type: RSA
Subkey-Length: 2048
Name-Real: %s
Name-Email: %s
%s
Expire-Date: 0
%%commit
%%echo Done"
name
email
(if (string-empty-p passphrase)
"%no-protection"
(format "Passphrase: %s" passphrase))))
(temp-file (make-temp-file "gpg-batch" nil ".txt")))
(with-temp-file temp-file
(insert batch-config))
(message "Creating GPG keypair, please wait...")
(let ((result (shell-command (format "\"%s\" --batch --generate-key \"%s\""
epg-gpg-program temp-file))))
(delete-file temp-file)
(if (= result 0)
(progn
(message "GPG keypair created successfully!")
(my/check-or-create-gpg-keys))
(message "Failed to create GPG keypair. Please check configuration")))))
(defun my/check-gpg-config ()
"检查和修复GPG配置文件。"
(interactive)
(let* ((gpg-home (or (getenv "GNUPGHOME")
(expand-file-name "~/.gnupg")))
(gpg-conf (expand-file-name "gpg.conf" gpg-home))
(gpg-agent-conf (expand-file-name "gpg-agent.conf" gpg-home)))
;; 确保.gnupg目录存在
(unless (file-directory-p gpg-home)
(make-directory gpg-home t)
(message "Created GPG home directory: %s" gpg-home))
;; 检查和创建gpg.conf
(unless (file-exists-p gpg-conf)
(with-temp-file gpg-conf
(insert "# GPG配置文件\n")
(insert "use-agent\n")
(insert "armor\n")
(insert "keyid-format long\n"))
(message "Created gpg.conf configuration file"))
;; 检查和创建gpg-agent.conf (Windows需要)
(unless (file-exists-p gpg-agent-conf)
(with-temp-file gpg-agent-conf
(insert "# GPG Agent配置文件\n")
(insert "default-cache-ttl 28800\n")
(insert "max-cache-ttl 86400\n")
(when (eq system-type 'windows-nt)
(insert "pinentry-program C:/Program Files (x86)/Gpg4win/bin/pinentry-basic.exe\n")))
(message "Created gpg-agent.conf configuration file"))
(message "GPG configuration check completed")))
(defun my/fix-epa-issues ()
"一键诊断和修复EPA配置问题。"
(interactive)
(message "Starting EPA diagnostics and repair...")
;; 重新配置GPG
(my/setup-gpg)
;; 检查配置文件
(my/check-gpg-config)
;; 检查密钥
(my/check-or-create-gpg-keys)
;; 重新初始化EPA
(when (fboundp 'epa-file-disable)
(epa-file-disable))
(epa-file-enable)
;; 测试EPA功能
(run-with-timer 3 nil (lambda ()
(condition-case err
(progn
(epg-make-context)
(message "EPA repair completed! Please test Mastodon functionality"))
(error
(message "EPA still has issues: %s" (error-message-string err))
(message "Suggestion: manually check GPG installation and key configuration"))))))
;; Initialize GPG configuration
(my/setup-gpg)
;; Mastodon Configuration
;; Social media client with modern UI
(unless (package-installed-p 'mastodon)
(package-install 'mastodon))
(with-eval-after-load 'mastodon
(setq mastodon-instance-url "https://m.otter.homes"
mastodon-active-user "cytrogen")
;; 界面设置
(setq mastodon-client-width-mode nil)
;; --- UI Modernization (Avatars & Images) ---
;; 1. Enable Avatars
(setq mastodon-tl--show-avatars t)
;; 2. Optimize Image Preview
(setq mastodon-media-format-type 'preview)
;; 智能EPA绕过和错误隔离
(defvar my/mastodon-encryption-attempted nil
"记录是否已尝试启用加密。")
(defun my/test-mastodon-encryption ()
"测试Mastodon是否能安全使用GPG加密。"
(condition-case err
(progn
(epg-make-context)
(epg-list-keys (epg-make-context))
t) ; 成功返回t
(error nil))) ; 失败返回nil
;; 根据EPA功能自动选择加密方式
(if (and (not my/mastodon-encryption-attempted)
(my/test-mastodon-encryption))
(progn
(setq mastodon-client-file-encryption t)
(setq my/mastodon-encryption-attempted t)
(message "Mastodon enabled GPG encrypted storage"))
(progn
(setq mastodon-client-file-encryption nil)
(setq my/mastodon-encryption-attempted t)
(message "Mastodon using plaintext storage (EPA unavailable or problematic)")))
;; 运行时错误隔离
(condition-case gpg-err
(when mastodon-client-file-encryption
(when (and epg-gpg-program (file-exists-p epg-gpg-program))
(message "Mastodon GPG configuration validation completed")))
(error
(setq mastodon-client-file-encryption nil) ; 自动fallback到明文
(message "Detected EPA issues, automatically switching to plaintext storage: %s" (error-message-string gpg-err)))))
;; Nerd Icons Integration
(unless (package-installed-p 'nerd-icons)
(package-install 'nerd-icons))
(defun my/verify-nerd-font ()
"Check if Symbols Nerd Font is available."
(interactive)
(let* ((font-name "Symbols Nerd Font Mono")
(font (find-font (font-spec :name font-name))))
(if font
(message "成功! Emacs 已找到字体: %s" font)
(message "失败! Emacs 找不到 '%s'。请确认已选择'为所有用户安装'并重启了 Emacs。" font-name))))
(with-eval-after-load 'nerd-icons
;; (setq nerd-icons-font-family "Your Nerd Font Name")
)
(with-eval-after-load 'mastodon
(require 'nerd-icons)
;; 1. Replace text symbols with Nerd Icons
(setq mastodon-tl--symbols
`((reply . (,(nerd-icons-faicon "nf-fa-reply") . "R"))
(boost . (,(nerd-icons-faicon "nf-fa-retweet") . "B"))
(reblog . (,(nerd-icons-faicon "nf-fa-retweet") . "B"))
(favourite . (,(nerd-icons-faicon "nf-fa-star") . "F"))
(bookmark . (,(nerd-icons-faicon "nf-fa-bookmark") . "K"))
(media . (,(nerd-icons-faicon "nf-fa-file_image_o") . "[media]"))
(verified . (,(nerd-icons-octicon "nf-oct-verified") . "V"))
(locked . (,(nerd-icons-faicon "nf-fa-lock") . "[locked]"))
(private . (,(nerd-icons-faicon "nf-fa-lock") . "[followers]"))
(mention . (,(nerd-icons-faicon "nf-fa-at") . "[mention]"))
(direct . (,(nerd-icons-faicon "nf-fa-envelope") . "[direct]"))
(edited . (,(nerd-icons-faicon "nf-fa-pencil") . "[edited]"))
(update . (,(nerd-icons-faicon "nf-fa-pencil") . "[edited]"))
(status . (,(nerd-icons-faicon "nf-fa-bell") . "[posted]"))
(poll . (,(nerd-icons-faicon "nf-fa-bar_chart") . "[poll]"))
(follow . (,(nerd-icons-faicon "nf-fa-user_plus") . "+"))
(follow_request . (,(nerd-icons-faicon "nf-fa-user_plus") . "+"))
(severed_relationships . (,(nerd-icons-faicon "nf-fa-chain_broken") . "//"))
(moderation_warning . (,(nerd-icons-faicon "nf-fa-exclamation_triangle") . "!!"))
(reply-bar . ("┃" . "|"))))
;; 2. Modernize Faces
;; Use variable width font for main text (if desired)
(add-hook 'mastodon-mode-hook #'variable-pitch-mode)
;; Make headers stand out
(set-face-attribute 'mastodon-display-name-face nil :height 1.2 :weight 'bold)
(set-face-attribute 'mastodon-handle-face nil :height 0.9 :slant 'italic :foreground "gray60")
;; Improve metadata visibility
(set-face-attribute 'mastodon-toot-docs-face nil :height 0.85 :foreground "gray50")
;; Fix alignment for icons if needed
(when (display-graphic-p)
(setq mastodon-tl--enable-proportional-fonts t)))
;; Security Tools
;; Additional security and privacy features
(defun my/check-gpg-status ()
"检查并显示当前GPG配置状态。"
(interactive)
(with-output-to-temp-buffer "*GPG状态检查*"
(princ "=== GPG状态检查 ===\n\n")
;; 使用与my/setup-gpg相同的查找逻辑
(princ "1. GPG程序检查(按优先级顺序):\n")
(let* ((candidates '("D:/GnuPG/bin/gpg.exe" ; 用户实际安装位置
"C:/Program Files (x86)/GnuPG/bin/gpg.exe"
"C:/Program Files/GnuPG/bin/gpg.exe"
"gpg"))
(found-paths '()))
;; 检查每个候选路径
(dolist (path candidates)
(let ((exists (if (string-match-p "/" path)
(file-exists-p path)
(executable-find path))))
(if exists
(progn
(princ (format " Found: %s\n" path))
(push path found-paths))
(princ (format " ✗ %s\n" path)))))
;; 显示当前使用的路径
(princ "\n2. 当前Emacs配置:\n")
(if epg-gpg-program
(progn
(princ (format " Using path: %s\n" epg-gpg-program))
(condition-case err
(let ((version (shell-command-to-string (format "\"%s\" --version" epg-gpg-program))))
(princ (format " Version: %s" (car (split-string version "\n")))))
(error (princ (format " Warning: version check failed: %s\n" (error-message-string err))))))
(princ " ✗ epg-gpg-program 未设置\n"))
(princ (format " epa-pinentry-mode: %s\n" epa-pinentry-mode))
;; Mastodon相关设置
(princ "\n3. Mastodon配置:\n")
(if (boundp 'mastodon-client-file-encryption)
(princ (format " Encryption setting: %s\n"
(if mastodon-client-file-encryption "GPG加密" "明文存储(当前设置)")))
(princ " ✓ 加密设置: 默认(依赖GPG)\n"))
;; 给出建议
(princ "\n=== 状态总结 ===\n")
(cond
((not (boundp 'mastodon-client-file-encryption))
(princ "⚠️ Mastodon包未加载,无法确定加密设置\n"))
((not mastodon-client-file-encryption)
(princ "OK: Mastodon configured for plaintext storage, should work normally\n")
(princ " For GPG encryption, run M-x my/test-gpg-basic to diagnose issues\n"))
((and epg-gpg-program (file-exists-p epg-gpg-program))
(princ "OK: GPG configured, mastodon should work with encryption\n"))
(found-paths
(princ "Warning: Found GPG but not properly configured, please restart Emacs\n"))
(t
(princ "Error: GPG not found, suggest enabling plaintext storage\n"))))
(princ "\nRun M-x my/setup-gpg to reconfigure GPG\n")))
(defun my/test-gpg-basic ()
"Test basic GPG functionality to help troubleshoot issues."
(interactive)
(with-output-to-temp-buffer "*GPG Basic Test*"
(princ "=== GPG Basic Functionality Test ===\n\n")
;; 测试GPG版本
(princ "1. GPG Version Test:\n")
(condition-case err
(let ((version-output (shell-command-to-string (format "\"%s\" --version" epg-gpg-program))))
(princ (format " Version info:\n%s\n" version-output)))
(error (princ (format " Error: Version check failed: %s\n" (error-message-string err)))))
;; 测试密钥列表
(princ "2. Key Check:\n")
(condition-case err
(let ((keys-output (shell-command-to-string (format "\"%s\" --list-keys" epg-gpg-program))))
(if (string-match "gpg: error" keys-output)
(princ " Warning: Key list has warnings, but this is usually normal\n")
(princ " Key list access normal\n")))
(error (princ (format " Error: Key check failed: %s\n" (error-message-string err)))))
;; 测试Emacs EPA
(princ "3. Emacs EPA Test:\n")
(condition-case err
(progn
(require 'epg)
(let ((context (epg-make-context)))
(princ " EPA context created successfully\n")))
(error (princ (format " Error: EPA test failed: %s\n" (error-message-string err)))))
;; 给出建议
(princ "\n=== Suggestions ===\n")
(princ "If GPG basic functionality works but mastodon still has issues:\n")
(princ "1. Plaintext storage is currently enabled, mastodon should work normally\n")
(princ "2. For full GPG support, you may need to generate GPG keypair\n")
(princ "3. Run: gpg --full-generate-key (in command line)\n")
(princ "4. Or continue using plaintext storage (usually secure enough for personal use)\n")))
(provide 'pkg-social)
;;; pkg-social.el ends here