diff --git a/idec-answers.el b/idec-answers.el new file mode 100644 index 0000000..fcfb16d --- /dev/null +++ b/idec-answers.el @@ -0,0 +1,232 @@ +;;; idec-answers.el --- This file part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: + +(require 'idec-mode) +(require 'web) + +(defun replace-in-string (what with str) + "Replace WHAT WITH in STR." + (replace-regexp-in-string (regexp-quote what) with str nil 'literal)) + +(defun base64-to-base64url (str) + "Make url safe base64 string STR." + (when (string-match (regexp-quote "=") str) + (and (base64-to-base64url (replace-regexp-in-string "=+$" "" str)))) + (when (string-match (regexp-quote "+") str) + (and (base64-to-base64url (replace-in-string "+" "-" str)))) + (when (string-match (regexp-quote "/") str) + (and (base64-to-base64url (replace-in-string "/" "_" str)))) + str) + +(defun base64url-to-base64 (str) + "Make base64 from urlsafe STR." + (when (string-match (regexp-quote "-") str) + (and (base64url-to-base64 (replace-in-string "-" "+" str)))) + (when (string-match (regexp-quote "_") str) + (and (base64url-to-base64 (replace-in-string "_" "/" str)))) + str) + +(defun base64url-encode-string (str) + "Decode base64 urlsafe string STR." + (message (concat "Base64url: " (base64-to-base64url (base64-encode-string str t)))) + (base64-to-base64url (base64-encode-string str t))) + +(defun base64url-decode-string (str) + "Encode base64 urlsafe string STR." + (base64-decode-string (base64url-to-base64 str))) + +(defun point-url () + "Return url with `idec-primary-node' to send messages." + (concat idec-primary-node "u/point")) + +(defun request-is-done (result) + "Show message with RESULT code." + (message "IDEC: Sended. Result: %S" result)) + +(defun do-post-request (url msg) + "Make POST request to URL with data MSG." + (message (gethash 'tmsg msg)) + (web-http-post + (lambda (con header data) + (request-is-done data)) + :url url + :data msg + ) + ;; (request + ;; url + ;; :type "POST" + ;; :data '(("pauth" . (gethash "path" msg)) ("tmsg" . (gethash "tmsg" msg))) + ;; ;; :headers '(("Content-Type" . "application/json")) + ;; ;; :parser 'json-read + ;; :success (function* + ;; (lambda (&key data &allow-other-keys) + ;; (message "I sent: %S" (assoc-default 'json data))))) + ) + +(defun post-message (encoded-message) + "Do POST request to `idec-primary-node' with Base64 ENCODED-MESSAGE." + (message (base64url-decode-string encoded-message)) + (let (json) + (setq json (make-hash-table :test 'equal)) + (puthash 'pauth idec-account-auth json) + (puthash 'tmsg encoded-message json) + (do-post-request (point-url) json)) + ;; (message (base64url-decode-string encoded-message)) + (message "Message sended")) + +(defun send-new-message (echo) + "Send new message to ECHO." + (switch-to-buffer (concat "*IDEC: New message to echo " echo "*")) + (let ((msg (make-hash-table :test 'equal))) + (puthash "body" + (encode-coding-string (s-join "\n" (-drop-last 1 (-drop 4 (split-string (buffer-string) "\n")))) + 'utf-8) + msg) + (puthash "subj" + (encode-coding-string (nth 1 (split-string (nth 2 (split-string (buffer-string) "\n")) "bj: ")) + 'utf-8) + msg) + (puthash "echo" echo msg) + + (do-send-new-post-request msg))) + +(defun do-send-new-post-request (msg) + "Make IDEC compatible point message MSG and send it to `idec-primary-node'." + (let (point-message) + (setq point-message (list + (gethash "echo" msg) + "All" + (gethash "subj" msg) + "" + (gethash "body" msg))) + ;; Encode message in Base64 + (post-message (base64url-encode-string (s-join "\n" point-message))))) + +(defun do-send-reply-post-request (message) + "Make IDEC compatible point MESSAGE and send it to `idec-primary-node'." + (message (gethash "body" message)) + (let (point-message) + (setq point-message (list + (gethash "echo" message) + (gethash "author" message) + (gethash "subj" message) + "" + (concat "@repto:" (gethash "id" message)) + (gethash "body" message))) + ;; Encode message in Base64 + (post-message (base64url-encode-string (encode-coding-string (s-join "\n" point-message) 'utf-8))) + (kill-buffer (concat "*IDEC: answer to " (gethash "id" message) "*")))) + +(defun send-reply-message (msg) + "Send message MSG to `idec-primary-node'." + (switch-to-buffer (concat "*IDEC: answer to " (gethash "id" msg) "*")) + (puthash "body" + (s-join "\n" (-drop-last 1 (-drop 4 (split-string (buffer-string) "\n")))) + msg) + (message (gethash "body" msg)) + (do-send-reply-post-request msg)) + +(defun get-answers-hash (id msg-hash) + "Make answers hashtable from ID and MSG-HASH." + (let (answer-hash) + (setq answer-hash (make-hash-table :test 'equal)) + (puthash "id" id answer-hash) + (puthash "echo" (get-message-field (gethash "content" msg-hash) "echo") answer-hash) + (puthash "author" (get-message-field (gethash "content" msg-hash) "author") answer-hash) + (puthash "time" (get-message-field (gethash "content" msg-hash) "time") answer-hash) + + (setq subj (get-message-field (gethash "content" msg-hash) "subj")) + + ;; Make `Re:' in subj if it not present. + (if (not (string-match "Re:" subj)) + (puthash "subj" (concat "Re: " subj) answer-hash) + (puthash "subj" subj answer-hash)) + answer-hash)) + +(defun make-answer-header (id msg-hash) + "Make header with reto to ID from MSG-HASH." + (let (answer-hash subj p) + (setq answer-hash (get-answers-hash id msg-hash)) + + (concat + (concat "Answer to " id " in " (gethash "echo" answer-hash) "\n") + (concat "Author: " + (gethash "author" answer-hash) + (concat " at " (gethash "time" answer-hash)) + "\n") + (concat "Subj: " (gethash "subj" answer-hash) "\n") + "------- YOU MESSAGE BELLOW -------\n"))) + +(defun edit-answer-without-quote (id msg-hash) + "Answer to message with ID MSG-HASH." + (let (answer-hash p) + (setq answer-hash (get-answers-hash id msg-hash)) + (switch-to-buffer (get-buffer-create (concat "*IDEC: answer to " id "*"))) + + (insert (make-answer-header id msg-hash)) + (forward-line) + (add-text-properties (point) (point-min) 'read-only) + (setq p (point)) + + (insert "\n") + (insert-text-button "[Send]" + 'action (lambda (x) (send-reply-message (button-get x 'msg))) + 'msg answer-hash) + (goto-char p) + (idec-mode))) + +;; END OF ANSWERS + +;; NEW MESSAGE +(defun make-new-message-header (echo) + "Return header for new message with filled ECHO field." + (concat + (concat "New message to echo " echo "\n") + "\n" + "Subj: \n" + "------- YOU MESSAGE BELLOW -------\n") + ) + +(defun edit-new-message (echo) + "Edit new message to ECHO." + (switch-to-buffer (get-buffer-create (concat "*IDEC: New message to echo " echo "*"))) + (insert (make-new-message-header echo)) + (forward-line) + + (let (p) + (setq p (point)) + (insert "\n") + (insert-text-button "[Send]" + 'action (lambda (x) (send-new-message (button-get x 'msg-echo))) + 'msg-echo echo) + (goto-char p)) + (idec-mode)) + +(provide 'idec-answers) + +;;; idec-answers.el ends here diff --git a/idec-customize.el b/idec-customize.el new file mode 100644 index 0000000..0ca09d4 --- /dev/null +++ b/idec-customize.el @@ -0,0 +1,133 @@ +;;; idec-customize.el --- This file part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: + +;; CUSTOMIZATION +;; ;;;;;;;;;;;;; + +(defgroup idec nil + "IDEC configuration." + :group 'network) + +;; Not used +(defcustom idec-nodes-list + '("http://idec.spline-online.tk/" + "https://ii-net.tk/ii/ii-point.php?q=/") + "List of IDEC nodes." + :type 'alist + :group 'idec) + +(defcustom idec-primary-node nil + "Primary node to send messages." + :type 'string + :group 'idec) + +;; Never used at this time. +(defcustom idec-use-list-txt t + "Use /list.txt extension." + :group 'idec) + +(defcustom idec-smart-fetch t + "Enable smat fetching; +Download only new messages; Not implemented." + :type 'boolean + :group 'idec) + +(defcustom idec-download-limit "50" + "Limit of download messages; +Not used if `idec-smart-fetching' is not nil." + :type 'string + :group 'idec) + +(defcustom idec-download-offset "-50" + "Offset of download messages; +Not used if `idec-smart-fetching' is not nil." + :type 'string + :group 'idec) + +(defcustom idec-echo-subscriptions nil + "List of subribes echoes." + :type 'string + :group 'idec) + +(defcustom idec-mail-dir "~/.emacs.d/idec-mail" + "Directory to store mail." + :type 'string + :group 'idec) + +(defcustom idec-online-download-limit idec-dowload-limit + "Download limit on online browsing; +Default to `idec-download-lmit'" + :type 'string + :group 'idec) + +(defcustom idec-online-download-offset idec-dowload-offset + "Download limit on online browsing; +Default to `idec-download-offset'" + :type 'string + :group 'idec) + +(defgroup idec-accounts nil + "IDEC accounts settings." + :group 'idec) + +(defcustom idec-account-nick nil + "Account nickname." + :type 'string + :group 'idec-accounts) + +(defcustom idec-account-node nil + "Node to send messages." + :type 'string + :group 'idec-accounts) + +(defcustom idec-account-auth nil + "Account authstring." + :type 'string + :group 'idec-accounts) + +;; END OF CUSTOMIZATION +;; ;;;;;;;;;;;;;;;;;;;; + +;; VARIABLES +;; ;;;;;;;;; + +(defvar smart-download-limit nil + "Used with `idec-smart-fetch'.") + +(defvar smart-download-offset nil + "Used with `idec-smart-fetch'.") + +(defvar new-messages-list nil + "New messages for display.") + +;; END OF VARIABLES +;; ;;;;;;;;;;;;;;;; + +(provide 'idec-customize) + +;;; idec-customize ends here diff --git a/idec-db.el b/idec-db.el new file mode 100644 index 0000000..feca0d4 --- /dev/null +++ b/idec-db.el @@ -0,0 +1,128 @@ +;;; idec-db.el --- This file is a part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: +(require 'emacsql) +(require 'emacsql-sqlite) +(require 'idec-parser) + +(defun create-echo-mail-dir (echo) + "Create ECHO directory inside `idec-mail-dir'." + (if (file-exists-p idec-mail-dir) + (message idec-mail-dir) + (mkdir idec-mail-dir)) + (if (file-exists-p (concat idec-mail-dir "/" echo)) + (message (concat idec-mail-dir "/" echo)) + (mkdir (concat idec-mail-dir "/" echo)))) + +(defun get-echo-dir (echo) + "Get ECHO dir from `idec-mail-dir'." + (concat idec-mail-dir (concat "/" echo))) + +(defun filename-to-store (content id) + "Make filename from CONTENT unixtime and ID." + (concat (nth 2 (split-string content)) "-" id)) + +(defun get-message-file (echo id) + "Get ECHO message filename by ID." + (concat (get-echo-dir echo) "/" id)) + +(defun get-counter-file (echo) + "Get ECHO counter filename." + (concat (get-echo-dir echo) "/counter")) + +(defun store-message (content echo id) + "Store CONTENT from ECHO message in `idec-mail-dir' with it ID." + (create-echo-mail-dir echo) + (write-region content nil (get-message-file echo id))) + +(defun store-echo-counter (echo) + "Store count messages in ECHO." + (create-echo-mail-dir echo) + (write-region (echo-messages-count echo) nil (get-counter-file echo))) + +(defun check-message-in-echo (msg echo) + "Check if exists message MSG in ECHO `idec-mail-dir'." + (not (f-file? (get-message-file echo msg)))) + +(defun open-echo-db (echo) + "Create or open sqlite database inside ECHO `idec-mail-dir'." + (emacsql-sqlite (concat (get-echo-dir echo) "/db.sqlite3"))) + +(defun create-db-schema (echo) + "Create db schema for ECHO." + (emacsql (open-echo-db echo) + [:create-table messages + ([(id :primary-key) + tags + author + to + echo + subj + (time :timestamp) + (body :text) + (unread integer :default 1)]) + ]) + t) + +(defun init-echo-db (echo) + "Initialize new database for ECHO." + (when (check-message-in-echo "db.sqlite3" echo) + (and + (create-db-schema echo) + (message (concat "IDEC: Database for " echo " initialized."))))) + +(defun insert-message-to-db (msg id &optional mark-read) + "Insert MSG ID to echo db; +unread by default, but you can MARK-READ it." + (if (not (emacsql (open-echo-db (get-message-field msg "echo")) + [:insert :into messages + :values ([$s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9])] + id + (get-message-field msg "tags") + (get-message-field msg "author") + (get-message-field msg "recipient") + (get-message-field msg "echo") + (get-message-field msg "subj") + (get-message-field msg "time") + (s-join "\n" (get-message-field msg "body")) + mark-read)) + (message (concat "IDEC: Message " id " stored in db")) + (message (concat "IDEC: Problem to store message " id)))) + +(defun check-message-in-db (msgid echo) + "Check message MSGID in ECHO database." + (if (not (emacsql (open-echo-db echo) + [:select [id] + :from messages + :where (= id $s1)] + msgid)) + t + nil)) + +(provide 'idec-db) + +;;; idec-db.el ends here diff --git a/idec-mode.el b/idec-mode.el new file mode 100644 index 0000000..0cfce60 --- /dev/null +++ b/idec-mode.el @@ -0,0 +1,83 @@ +;;; idec-mode.el --- This file part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: + +;; MODE +;; ;;;; + +(defun idec-close-message-buffer () + "Close buffer with message." + (kill-this-buffer)) + +(defvar idec-mode-hook nil) + +(defvar idec-mode-map + (let ((map (make-keymap))) + (define-key map "\C-c \C-c" 'kill-this-buffer) + (define-key map "\C-c \C-n" 'idec-next-message) + (define-key map "\C-c \C-b" 'idec-previous-message) + map) + "Keymapping for IDEC mode.") + +(defconst idec-font-lock-keywords-1 + (list + '("\\(ID:.*\\)" . font-lock-function-name-face) + '("\\<\\(\\(?:Echo\\|From\\|Subj\\|T\\(?:ime\\|o\\)\\):\\)\\>" . font-lock-function-name-face)) + "Minimal highlighting expressions for IDEC mode.") + +(defconst idec-font-lock-keywords-2 + (append idec-font-lock-keywords-1 (list + '("\\<\\(>>?.*\\)\s\\>" . font-lock-comment-face))) + "Quotes highligting for IDEC mode.") + +(defvar idec-font-lock-keywords idec-font-lock-keywords-2 + "Default highlighting expressions for IDEC mode.") + +(defvar idec-mode-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?_ "w" st) + (modify-syntax-entry ?/ ". 2b" st) + st)) + +;; Mode function +(defun idec-mode () + "Major mode for view and editing IDEC messages." + (interactive) + (kill-all-local-variables) + ;; Mode definition + (set-syntax-table idec-mode-syntax-table) + (use-local-map idec-mode-map) + ;; (font-lock-add-keywords 'idec-mode '(idec-font-lock-keywords)) + ;; (set (make-local-variable 'font-lock-defaults) '(idec-font-lock-keywords)) + (setq major-mode 'idec-mode) + (setq mode-name "[IDEC]") + (setq imenu-generic-expression "*IDEC") + (run-hooks 'idec-mode-hook)) + +(provide 'idec-mode) + +;;; idec-mode.el ends here diff --git a/idec-online.el b/idec-online.el new file mode 100644 index 0000000..7284ffe --- /dev/null +++ b/idec-online.el @@ -0,0 +1,124 @@ +;;; idec-online.el --- This file part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: + +(require 'idec-mode) +(require 'idec-answers) + +(defun display-echo-messages (messages) + "Display downloaded MESSAGES from echo." + (message (concat "RECEIVED MESSAGES: " messages)) + (let (msgs echo echo-msg-hash) + (setq echo-msg-hash (make-hash-table :test 'equal)) + (setq echo (nth 0 (split-string messages "\n"))) + (setq msgs (split-string messages "\n")) + (dolist (id msgs) + (when (not (or + (string-match "\\." id) + (string= "" id))) + (puthash id echo echo-msg-hash))) + (with-output-to-temp-buffer (get-buffer-create (concat "*IDEC: online browse " echo "*" )) + (switch-to-buffer (concat "*IDEC: online browse " echo "*")) + (maphash (lambda (id msg-hash) + (when (equal (get-message-field (gethash "content" msg-hash) "echo") echo) + (princ "__________________________________\n") + (princ (concat "ID: " id "\n")) + (princ (concat "From: " (get-message-field (gethash "content" msg-hash) "author") "(" + (get-message-field (gethash "content" msg-hash) "address") ")" "\n")) + (princ (concat "To: " (get-message-field (gethash "content" msg-hash) "recipient") "\n")) + (princ (concat "Echo: " (get-message-field (gethash "content" msg-hash) "echo") "\n")) + (princ (concat "At: " (get-message-field (gethash "content" msg-hash) "time") "\n")) + (princ (concat "Subject: " (get-message-field (gethash "content" msg-hash) "subj") "\n")) + (princ (concat "__________________________________\n\n" + (s-join "\n" (get-message-field (gethash "content" msg-hash) "body")))) + (princ "\n__________________________________\n") + (princ "[") + (insert-button "Answer" + 'action (lambda (x) (edit-answer-without-quote (button-get x 'id) (button-get x 'msg-hash))) + 'id id + 'msg-hash msg-hash) + (princ "]") + (princ "\t [") + (insert-button "Answer with quote") + (princ "]\n\n"))) + ;; Plain messages hash proccesing + (get-messages-content echo-msg-hash)) + (idec-mode))) + (add-text-properties (point-min) (point-max) 'read-only)) + + +(defun load-echo-messages (echo &optional online) + "Load messages from ECHO with ONLINE selector." + (when (not online) + (message (concat "Update counter of " echo)) + (store-echo-counter echo)) + (display-echo-messages (get-url-content (make-echo-url echo)))) + +(defun proccess-echo-message (msg echo) + "Download new message MSG in ECHO." + (with-output-to-temp-buffer (get-buffer-create "*IDEC: DEBUG*") + (switch-to-buffer "*IDEC: DEBUG*") + (princ msg) + (princ echo))) + +(defun proccess-echo-list (raw-list) + "Parse RAW-LIST from HTTP response." + (with-output-to-temp-buffer (get-buffer-create "*IDEC: list.txt*") + (switch-to-buffer "*IDEC: list.txt*") + (dolist (line (split-string (decode-coding-string raw-list 'utf-8) "\n")) + (when (not (equal line "")) + ;; Defind echo + (defvar current-echo nil) + (setq current-echo (nth 0 (split-string line ":"))) + ;; Create clickable button + (insert-text-button current-echo + 'action (lambda (x) (load-echo-messages (button-get x 'echo) t)) + 'help-echo (concat "Go to echo " current-echo) + 'echo current-echo) + (princ (format "\t\t||%s\t\t%s\n" + (nth 2 (split-string line ":")) + (nth 1 (split-string line ":"))))) + )) + (idec-mode)) + +(defun idec-fetch-echo-list (nodeurl) + "Fetch echoes list from remote NODEURL." + (proccess-echo-list (get-url-content nodeurl))) + +(defun idec-online-browse () + "Load echoes list.txt from node `idec-primary-node'." + (interactive) + (idec-fetch-echo-list (concat idec-primary-node "list.txt"))) + +(defun idec-online-browse-hidden () + "Browse hidden echo." + (interactive) + (load-echo-messages (read-string "Enter echo name: ") t)) + +(provide 'idec-online) + +;;; idec-online.el ends here diff --git a/idec-parser.el b/idec-parser.el new file mode 100644 index 0000000..1497f5c --- /dev/null +++ b/idec-parser.el @@ -0,0 +1,106 @@ +;;; idec-parser.el --- This file part of GNU Emacs client for IDEC network + +;; Copyright (c) 2017 Denis Zheleztsov + +;; Author: Denis Zheleztsov +;; Keywords: lisp,network,IDEC +;; Version: 0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; In active developent. +;; Fetched node must be support modern IDEC extensions like /list.txt, /x/c, etc. + +;;; Code: + +;; Message fields pasing +(defun trim-string (string) + "Remove white spaces in beginning and ending of STRING; +White space here is any of: space, tab, Emacs newline (line feed, ASCII 10)." + (replace-regexp-in-string "\\`[ \t\n]*" "" (replace-regexp-in-string "[ \t\n]*\\'" "" string))) + +(defun get-message-tags (msg) + "Get MSG tags." + ;; (trim-string + (nth 0 (split-string msg "\n"))) + +(defun get-message-echo (msg) + "Get MSG echo." + ;; (trim-string + (nth 1 (split-string msg "\n"))) + +(defun get-message-time (msg) + "Get MSG time." + ;; ;; (trim-string + (current-time-string + (car (read-from-string (nth 2 (split-string msg "\n")))))) + +(defun get-message-author (msg) + "Get MSG author." + ;; ;; (trim-string + (nth 3 (split-string msg "\n"))) + +(defun get-message-address (msg) + "Get MSG address." + ;; (trim-string + (nth 4 (split-string msg "\n"))) + +(defun get-message-recipient (msg) + "Get MSG recipient." + ;; (trim-string + (nth 5 (split-string msg "\n"))) + +(defun get-message-subj (msg) + "Get MSG subject." + ;; (trim-string + (nth 6 (split-string msg "\n"))) + +(defun get-message-body (msg) + "Get MSG body text. +Return list with body content." + (-drop 8 (split-string msg "\n"))) + +(defun get-longest-field (field msg-list) + "Return longest FIELD in MSG-LIST." + (defvar field-legth '()) + (defvar field-max nil) + (setq field-max 0) + (maphash (lambda (id msg) + (when (> (length (get-message-field msg field)) + field-max) + (setq field-max (length (get-message-field msg field))))) + msg-list) + field-max) + +(defun get-message-field (msg field) + "Get message MSG FIELD." + (defvar fields-hash (make-hash-table :test 'equal) + "Hashtable with MSG parsing functions.") + + ;; Define hashtable first + (puthash "tags" (get-message-tags msg) fields-hash) + (puthash "echo" (get-message-echo msg) fields-hash) + (puthash "time" (get-message-time msg) fields-hash) + (puthash "author" (get-message-author msg) fields-hash) + (puthash "address" (get-message-address msg) fields-hash) + (puthash "recipient" (get-message-recipient msg) fields-hash) + (puthash "subj" (get-message-subj msg) fields-hash) + (puthash "body" (get-message-body msg) fields-hash) + (gethash field fields-hash)) + +(provide 'idec-parser) + +;;; idec-parser.el ends here diff --git a/idec.el b/idec.el index d7e5984..2e1d50c 100644 --- a/idec.el +++ b/idec.el @@ -26,8 +26,11 @@ ;;; Code: -;; CUSTOMIZATION -;; ;;;;;;;;;;;;; +(require 'idec-mode) +;; (require 'idec-answers) +(require 'idec-parser) +(require 'idec-online) +(require 'idec-db) (defgroup idec nil "IDEC configuration." @@ -50,18 +53,26 @@ "Use /list.txt extension." :group 'idec) +(defcustom idec-smart-fetch t + "Enable smat fetching; +Download only new messages; Not implemented." + :type 'boolean + :group 'idec) + (defcustom idec-download-limit "50" - "Limit of download messages." + "Limit of download messages; +Not used if `idec-smart-fetching' is not nil." :type 'string :group 'idec) (defcustom idec-download-offset "-50" - "Offset of download messages." + "Offset of download messages; +Not used if `idec-smart-fetching' is not nil." :type 'string :group 'idec) (defcustom idec-echo-subscriptions nil - "List(comma separated) of subribtions." + "List of subribes echoes." :type 'string :group 'idec) @@ -70,21 +81,33 @@ :type 'string :group 'idec) +(defcustom idec-online-download-limit "0" + "Download limit on online browsing; +Default to `idec-download-lmit'" + :type 'string + :group 'idec) + +(defcustom idec-online-download-offset "0" + "Download limit on online browsing; +Default to `idec-download-offset'" + :type 'string + :group 'idec) + (defgroup idec-accounts nil "IDEC accounts settings." :group 'idec) -(defcustom idec-account-nick nil +(defcustom idec-account-nick "" "Account nickname." :type 'string :group 'idec-accounts) -(defcustom idec-account-node nil +(defcustom idec-account-node "" "Node to send messages." :type 'string :group 'idec-accounts) -(defcustom idec-account-auth nil +(defcustom idec-account-auth "" "Account authstring." :type 'string :group 'idec-accounts) @@ -92,120 +115,264 @@ ;; END OF CUSTOMIZATION ;; ;;;;;;;;;;;;;;;;;;;; +;; VARIABLES +;; ;;;;;;;;; + +(defvar smart-download-limit nil + "Used with `idec-smart-fetch'.") + +(defvar smart-download-offset nil + "Used with `idec-smart-fetch'.") + +(defvar new-messages-list nil + "New messages for display.") + +(setq idec-online-download-limit idec-download-limit) +(setq idec-online-download-offset idec-download-offset) + +;; END OF VARIABLES +;; ;;;;;;;;;;;;;;;; + +;; NAVIGATION FUNCTIONS +;; ;;;;;;;;;;;;;;;;;;;; + +(defun idec-next-message () + "Show next message." + (interactive) + (kill-this-buffer) + (forward-button 1) + (push-button)) + +(defun idec-previous-message () + "Show next message." + (interactive) + (kill-this-buffer) + (backward-button 1) + (push-button)) + +;; END OF NAVIGATION FUNCTIONS +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; END OF MODE +;; ;;;;;;;;;;; + ;; FUNCTIONS ;; ;;;;;;;;; -;; MAIL FUNCTIONS -;; ;;;;;;;;;;;;;; -(defun idec-create-mail-echo-dir (echo) - "Create ECHO directory inside idec-mail-dir." - (mkdir echo idec-mail-dir)) - ;; (mkdir (concat idec-mail-dir (concat "/" echo)))) - -;; END OF MAIL FUNCTIONS -;; ;;;;;;;;;;;;;;;;;;;;; - -(defun idec-load-new-messages () - "Load new messages from IDEC nodes Not implemented." - (interactive) - (download-subscriptions)) - -;; ECHOES FUNCTIONS -;; ;;;;;;;;;;;;;;;; - -(defun download-subscriptions () - "Download subs." +(defun get-url-content (url) + "Get URL content and return it without headers." (with-current-buffer - (url-retrieve-synchronously (make-echo-url (split-string idec-echo-subscriptions ","))) + (url-retrieve-synchronously url) (goto-char (point-min)) (re-search-forward "^$") (forward-line) (delete-region (point) (point-min)) - (beginning-of-line) - (setq echo-start (point)) - (goto-char (point-min)) - (re-search-forward "^.+\..+$") - (setq echo-end (point)) - (idec-create-mail-echo-dir (kill-region echo-start echo-end)) - (display-subscriptions (buffer-string)))) + (buffer-string))) -(defun idec-load-subscriptions () - "Load messages id from subscriptions." - (with-current-buffer - (url-retrieve-synchronously (make-echo-url (split-string idec-echo-subscriptions ","))) - (goto-char (point-min)) - (re-search-forward "^$") - (delete-region (point) (point-min)) - (display-subscriptions (buffer-string)))) +;; LOCAL MAIL FUNCTIONS +;; ;;;;;;;;;;;;;;;;;;;; -(defun display-subscriptions (messages) - "Display downloaded MESSAGES from echo." - (with-output-to-temp-buffer (get-buffer-create "*IDEC: browse subs*") - (switch-to-buffer "*IDEC: browse subs*") - (princ messages))) +(defun get-local-echoes () + "Get local downloaded echoes from `idec-mail-dir'." + (delete '".." (delete '"." (directory-files idec-mail-dir nil "\\w*\\.\\w*")))) -(defun make-echo-url (echoes) - "Make ECHOES url to retreive messages id." - (defvar echoes-seq (make-list 20 0) "temp sequence.") - ;; Check ECHOES is list - (if (listp echoes) - ;; Required GNU Emacs >= 25.3 - (concat idec-primary-node "u/e/" - (string-join echoes "/") "/" idec-download-offset ":" idec-download-limit) - (concat idec-primary-node "u/e/" echoes "/" idec-download-offset ":" idec-download-limit))) +(defun idec-browse-local-mail () + "Browse local mail from `idec-mail-dir'." + (message (s-join " " (get-local-echoes)))) -(defun display-echo-messages (messages) - "Display downloaded MESSAGES from echo." - (with-output-to-temp-buffer (get-buffer-create "*IDEC: browse echo*") - (switch-to-buffer "*IDEC: browse echo*") - (princ messages))) +;; END OF LOCAL MAIL FUNCTIONS +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun load-echo-messages (echo) - "Load messages from ECHO." - (with-current-buffer - (url-retrieve-synchronously (make-echo-url echo)) - (goto-char (point-min)) - (re-search-forward "^$") - (delete-region (point) (point-min)) - (display-echo-messages (buffer-string)))) - -(defun proccess-echo-list (raw-list) - "Parse RAW-LIST from HTTP response." - (with-output-to-temp-buffer (get-buffer-create "*IDEC: list.txt*") - (switch-to-buffer "*IDEC: list.txt*") - (dolist (line (split-string (decode-coding-string raw-list 'utf-8) "\n")) - (when (not (equal line "")) - ;; Defind echo - (defvar current-echo nil) - (setq current-echo (nth 0 (split-string line ":"))) - ;; Create clickable button - (insert-text-button current-echo - 'action (lambda (x) (load-echo-messages (button-get x 'echo))) - 'help-echo (concat "Go to echo " current-echo) - 'echo current-echo) - (princ (format "\t\t||%s\t\t%s\n" - (nth 2 (split-string line ":")) - (nth 1 (split-string line ":"))))) - ))) - -(defun idec-fetch-echo-list (nodeurl) - "Fetch echoes list from remote NODEURL." - (with-current-buffer - (url-retrieve-synchronously nodeurl) - (goto-char (point-min)) - (re-search-forward "^$") - (delete-region (point) (point-min)) - (proccess-echo-list (buffer-string)))) - -(defun idec-load-echoes () - "Load echoes list from node." +(defun idec-load-new-messages () + "Load new messages from IDEC `idec-primary-node'." (interactive) - (idec-fetch-echo-list (concat idec-primary-node "list.txt"))) + (defvar current-echo nil) + (setq new-messages-list (make-hash-table :test 'equal)) + (let (msgid-for-download) + (setq msgid-for-download (make-hash-table :test 'equal)) + (dolist (line (split-string (download-subscriptions) "\n")) + (if (string-match "\\." line) + (and (setq current-echo line) + (store-echo-counter line)) + (when (and (check-message-in-echo line current-echo) + (> (length line) 1)) + (when (not (string= "" line)) + (puthash line current-echo msgid-for-download))))) + (download-message msgid-for-download)) + ;; (print (hash-table-count new-messages-list)) + ;; (message (gethash "id" (nth 0 new-messages-list))) + (display-new-messages) + ) + +(defun display-message (msg) + "Display message MSG in new buffer in idec-mode." + (with-output-to-temp-buffer (get-buffer-create (concat "*IDEC: " + (decode-coding-string + (get-message-field + (gethash "msg" msg) "subj") + 'utf-8) + "*")) + ;; Run in IDEC mode + (switch-to-buffer (concat "*IDEC: " (decode-coding-string (get-message-field + (gethash "msg" msg) "subj") + 'utf-8) + "*")) + (princ (concat "ID: " (gethash "id" msg) "\n")) + (princ (concat "From: " (get-message-field (gethash "msg" msg) "author") "(" + (get-message-field (gethash "msg" msg) "address") ")" "\n")) + (princ (concat "To: " (get-message-field (gethash "msg" msg) "recipient") "\n")) + (princ (concat "Echo: " (get-message-field (gethash "msg" msg) "echo") "\n")) + (princ (concat "At: " (get-message-field (gethash "msg" msg) "time") "\n")) + (princ (concat "Subject: " (get-message-field (gethash "msg" msg) "subj") "\n")) + (princ (concat "__________________________________\n\n" + (s-join "\n" (get-message-field (gethash "msg" msg) "body")))) + (princ "\n__________________________________\n") + (princ "[") + (let (answer-hash) + (setq answer-hash (make-hash-table :test 'equal)) + (puthash "content" (gethash "msg" msg) answer-hash) + (insert-button "Answer" + 'action (lambda (x) (edit-answer-without-quote (button-get x 'id) (button-get x 'msg-hash))) + 'id (gethash "id" msg) + 'msg-hash answer-hash)) + (princ "]") + (princ "\t [") + (insert-button "Quote answer") + (princ "]") + (add-text-properties (point-min) (point-max) 'read-only)) + (point-max) + (idec-mode)) + +(defun display-new-messages () + "Display new fetched messages from `new-messages-list'." + (if (= (hash-table-count new-messages-list) 0) + (message "IDEC: No new messages.") + (with-output-to-temp-buffer (get-buffer-create "*IDEC: New messages*") + (switch-to-buffer "*IDEC: New messages*") + + (maphash (lambda (id msg) + (let (m) + (setq m (make-hash-table :test 'equal)) + (puthash "id" id m) + (puthash "msg" msg m) + ;; Write message subj + (insert-text-button (concat (get-message-field msg "subj") + (make-string + (- (get-longest-field "subj" new-messages-list) + (length (get-message-field msg "subj"))) + ? )) + 'help-echo "Read message" + 'msg-hash m + 'action (lambda (x) (display-message (button-get x 'msg-hash))))) + ;; Write message time and echo + (princ (format " %s(%s)%s%s\t%s\n" + (get-message-field msg "author") + (get-message-field msg "address") + (make-string (- + (+ + (get-longest-field "author" new-messages-list) + (get-longest-field "address" new-messages-list) + 1) + (+ + (length (get-message-field msg "author")) + (length (get-message-field msg "address"))) + ) + ? ) + (get-message-field msg "echo") + (get-message-field msg "time")))) + new-messages-list)) + (idec-mode))) + +(defun hash-table-keys (hash-table) + "Get list of keys from HASH-TABLE." + (let ((keys ())) + (maphash (lambda (k v) (push k keys)) hash-table) + keys)) + +(defun get-messages-content (messages) + "Get MESSAGES content from `idec-primary-node'." + (let (new-hash) + (setq new-hash (make-hash-table :test 'equal)) + ;; (message (get-url-content (make-messages-url (hash-table-keys messages)))) + (dolist (line (split-string (get-url-content (make-messages-url (hash-table-keys messages))) "\n")) + (when (not (string= "" line)) + (let (msgid content mes) + (setq mes (make-hash-table :test 'equal)) + (setq msgid (nth 0 (split-string line ":"))) + (setq content + (decode-coding-string + (base64-decode-string + (nth 1 (split-string line ":"))) + 'utf-8)) + ;; Populate message hash: {"echo": "echo name", "content": "message content"} + (puthash "echo" (get-message-field content "echo") mes) + (puthash "content" content mes) + (puthash msgid mes new-hash)))) + new-hash)) + +(defun download-message (ids) + "Download messages with IDS to `idec-mail-dir'." + (if (= (hash-table-count ids) 0) + nil + (maphash (lambda (id msg) + (store-message (gethash "content" msg) (gethash "echo" msg) id) + (puthash id (gethash "content" msg) new-messages-list)) + (get-messages-content ids)))) + +(defun download-subscriptions () + "Download messages from echoes defined in `idec-echo-subscriptions' from `idec-primary-node'." + (message (make-echo-url (split-string idec-echo-subscriptions ","))) + (message idec-echo-subscriptions) + (get-url-content + (make-echo-url (split-string idec-echo-subscriptions ",")))) + +;; ECHOES FUNCTIONS +;; ;;;;;;;;;;;;;;;; + +(defun make-echo-url (echoes &optional online) + "Make ECHOES url to retreive messages from `idec-primary-node'; +with `idec-download-offset' and `idec-download-limit'; +If ONLINE is t uses `idec-online-download-limit' and `idec-online-download-offset'." + ;; Check ECHOES is list + (let (limit offset) + (if online + (and (setq limit idec-online-download-limit) + (setq offset idec-online-download-offset)) + (and (setq limit idec-download-limit) + (setq offset idec-download-offset))) + (if (listp echoes) + ;; Required GNU Emacs >= 25.3 + (message (concat idec-primary-node "u/e/" + (s-join "/" echoes) "/" offset ":" limit)) + (message (concat idec-primary-node "u/e/" echoes "/" offset ":" limit))))) + +(defun make-messages-url (messages) + "Make MESSAGES url to retreive messages from `idec-primary-node'." + ;; Check MESSAGES is list + (if (listp messages) + ;; Required GNU Emacs >= 25.3 + (concat idec-primary-node "u/m/" (s-join "/" messages)) + (concat idec-primary-node "u/m/" messages))) + +(defun make-count-url (echo) + "Return messages count url in `idec-primary-node' from ECHO." + (concat idec-primary-node "/x/c/" echo)) + +(defun echo-messages-count (echo) + "Get messages count in ECHO." + (nth 1 (split-string + (get-url-content (make-count-url echo)) ":"))) + ;; END OF ECHOES FUNCTIONS ;; ;;;;;;;;;;;;;;;;;;;;;;; +(defun idec-new-message () + "Make new message." + (interactive) + (edit-new-message (read-string "Echo: "))) + ;; END OF FUNCTIONS ;; ;;;;;;;;;;;;;;;; diff --git a/todo.org b/todo.org new file mode 100644 index 0000000..ae145a5 --- /dev/null +++ b/todo.org @@ -0,0 +1,4 @@ +* TODO idec-mode +* TODO Formating with highlighting header +* TODO Read local mail instead of downloading into RAM +* TODO Use smart-fetching